From ca273ba1ca51b0fb247ba0852a1fc6bb1156f2b7 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sat, 25 Jul 2026 21:46:51 +0200 Subject: [PATCH] Move the documentation into the cgp-knowledge-base repository The construct reference, the concepts, the guides, the error catalog, and the macro-implementation notes now live in the sibling `cgp-knowledge-base` repository under its `cgp/` directory. The worked examples, the related-work comparisons, and the communication-strategy documents sit at that base's top level, since they serve the whole ecosystem rather than this project, and the `/cgp` skill moved to `cgp-skills`, where a skill deployed on its own belongs. Every reference into the old `docs/` tree becomes a knowledge-base pointer: a link in a markdown file is a `main`-branch GitHub URL, and a doc pointer in a source comment or a test header names its path in the base. AGENTS.md now describes the sibling projects in place of the two `cgp`/`cargo-cgp` projects, including the rule that carries a construct change out to both the knowledge base and the skill, and the new sibling-projects.md records where each project lives and which revision of it to read. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/skills/cgp | 2 +- AGENTS.md | 136 ++- README.md | 2 + crates/macros/cgp-macro-core/AGENTS.md | 2 +- .../src/types/attributes/prefix.rs | 2 +- .../attributes/use_provider/attribute.rs | 2 +- .../src/types/cgp_auto_getter/blanket.rs | 2 +- .../preprocessed/to_consumer_impl.rs | 4 +- .../preprocessed/to_provider_blanket_impl.rs | 2 +- .../preprocessed/to_provider_trait.rs | 2 +- .../derive_builder/finalize_build_impl.rs | 2 +- .../derive_builder/has_builder_impl.rs | 2 +- .../derive_builder/has_field_impls.rs | 3 +- .../derive_builder/into_builder_impl.rs | 2 +- .../cgp_data/derive_builder/partial_data.rs | 2 +- .../derive_builder/update_field_impls.rs | 3 +- .../derive_extractor/extract_field_impls.rs | 3 +- .../derive_extractor/finalize_extract_impl.rs | 2 +- .../derive_extractor/has_extractor_impl.rs | 6 +- .../cgp_data/derive_extractor/partial_data.rs | 2 +- .../src/types/cgp_data/derive_from_variant.rs | 2 +- .../src/types/cgp_data/derive_has_field.rs | 2 +- .../cgp_data/derive_has_fields/derive_enum.rs | 2 +- .../derive_has_fields/derive_struct.rs | 2 +- .../src/types/cgp_fn/preprocessed.rs | 2 +- .../cgp-macro-core/src/types/cgp_impl/item.rs | 2 +- .../src/types/cgp_impl/lowered.rs | 2 +- .../cgp-macro-core/src/types/cgp_type/item.rs | 2 +- crates/tests/AGENTS.md | 24 +- crates/tests/README.md | 2 +- .../tests/invalid_expansion_tests.rs | 2 +- .../tests/parser_rejections/cgp_component.rs | 5 +- .../tests/parser_rejections/cgp_fn.rs | 4 +- .../tests/parser_rejections/cgp_impl.rs | 4 +- .../tests/parser_rejections/cgp_namespace.rs | 5 +- .../tests/parser_rejections/cgp_provider.rs | 5 +- .../parser_rejections/check_components.rs | 4 +- .../parser_rejections/delegate_components.rs | 4 +- .../parser_rejections/derive_cgp_data.rs | 2 +- .../parser_rejections/derive_from_variant.rs | 4 +- .../tests/parser_rejections/getters.rs | 5 +- .../tests/parser_rejections/use_type.rs | 5 +- .../tests/abstract_types/cgp_type_bounded.rs | 3 +- .../tests/abstract_types/cgp_type_macro.rs | 3 +- .../cgp_type_self_referential.rs | 3 +- .../tests/abstract_types/cgp_type_unsized.rs | 3 +- .../tests/abstract_types/extend_component.rs | 3 +- .../abstract_types/use_type_auto_getter.rs | 3 +- .../abstract_types/use_type_component.rs | 5 +- .../tests/abstract_types/use_type_fn_alias.rs | 3 +- .../use_type_fn_deep_foreign.rs | 3 +- .../abstract_types/use_type_fn_equality.rs | 3 +- .../use_type_fn_equality_cross_trait.rs | 3 +- .../abstract_types/use_type_fn_extend.rs | 5 +- .../abstract_types/use_type_fn_foreign.rs | 3 +- .../use_type_fn_foreign_equality.rs | 3 +- ...se_type_fn_foreign_equality_cross_trait.rs | 3 +- .../use_type_fn_nested_foreign.rs | 3 +- .../use_type_fn_reverse_order.rs | 3 +- .../tests/abstract_types/use_type_foreign.rs | 3 +- .../abstract_types/use_type_foreign_getter.rs | 4 +- .../abstract_types/use_type_generic_param.rs | 3 +- .../abstract_types/use_type_path_qualified.rs | 3 +- .../use_type_uses_supertrait.rs | 4 +- .../cgp-tests/tests/abstract_types_tests.rs | 6 +- .../tests/async_and_send/cgp_fn_async.rs | 2 +- .../cgp-tests/tests/async_and_send/spawn.rs | 3 +- .../cgp-tests/tests/async_and_send_tests.rs | 5 +- .../tests/basic_delegation/component_macro.rs | 6 +- .../consumer_delegate_generic.rs | 2 +- .../consumer_delegate_getter.rs | 2 +- .../tests/basic_delegation/default_methods.rs | 7 +- .../basic_delegation/delegate_array_key.rs | 2 +- .../delegate_components_macro.rs | 2 +- .../delegate_generic_nested_value.rs | 2 +- .../delegate_generic_table.rs | 2 +- .../delegate_nested_use_delegate.rs | 4 +- .../delegate_new_array_key.rs | 2 +- .../delegate_new_generic_struct.rs | 2 +- .../basic_delegation/delegate_new_struct.rs | 2 +- .../tests/basic_delegation/impl_self.rs | 4 +- .../tests/basic_delegation/owned_receiver.rs | 4 +- .../provider_component_override.rs | 2 +- .../tests/basic_delegation/provider_macro.rs | 2 +- .../tests/basic_delegation/self_in_macro.rs | 4 +- .../basic_delegation/self_in_nested_item.rs | 4 +- .../cgp-tests/tests/basic_delegation_tests.rs | 4 +- .../tests/blanket_traits/associated_type.rs | 2 +- .../blanket_traits/associated_type_bounded.rs | 2 +- .../cgp-tests/tests/blanket_traits/basic.rs | 2 +- .../tests/blanket_traits/with_method.rs | 2 +- .../cgp-tests/tests/blanket_traits_tests.rs | 2 +- .../cgp-tests/tests/checking/check_generic.rs | 4 +- .../tests/checking/check_path_context.rs | 4 +- .../tests/checking/check_providers.rs | 4 +- .../cgp-tests/tests/checking/check_trait.rs | 4 +- .../checking/delegate_and_check_basic.rs | 4 +- .../checking/delegate_and_check_generic.rs | 4 +- .../delegate_and_check_generic_key.rs | 4 +- .../checking/delegate_and_check_params.rs | 4 +- .../tests/cgp-tests/tests/checking_tests.rs | 7 +- .../auto_dispatch_async_generics.rs | 2 +- .../auto_dispatch_async_multi_args.rs | 2 +- ...to_dispatch_async_multi_args_owned_self.rs | 2 +- .../auto_dispatch_async_multi_args_ref.rs | 2 +- .../auto_dispatch_async_self_mut_only.rs | 2 +- .../auto_dispatch_async_self_only.rs | 2 +- .../auto_dispatch_async_self_ref_only.rs | 2 +- .../dispatching/auto_dispatch_generics.rs | 2 +- .../dispatching/auto_dispatch_multi_args.rs | 2 +- .../auto_dispatch_multi_args_owned_self.rs | 2 +- .../auto_dispatch_multi_args_ref.rs | 2 +- .../auto_dispatch_multi_methods.rs | 2 +- .../auto_dispatch_self_mut_only.rs | 2 +- .../dispatching/auto_dispatch_self_only.rs | 2 +- .../auto_dispatch_self_ref_only.rs | 2 +- ...o_dispatch_self_ref_return_explicit_ref.rs | 2 +- ...o_dispatch_self_ref_return_implicit_ref.rs | 2 +- .../tests/dispatching/auto_dispatch_shape.rs | 4 +- .../cgp-tests/tests/dispatching/compose.rs | 4 +- .../cgp-tests/tests/dispatching/types.rs | 2 +- .../tests/dispatching/use_delegate_getter.rs | 4 +- .../cgp-tests/tests/dispatching_tests.rs | 8 +- .../extensible_records/generic_record.rs | 2 +- .../extensible_records/optional_builder.rs | 4 +- .../tests/extensible_records/person_record.rs | 4 +- .../tests/extensible_records/point_cast.rs | 4 +- .../tests/extensible_records/product_value.rs | 4 +- .../extensible_records/record_build_from.rs | 4 +- .../record_build_with_handlers.rs | 4 +- .../tests/extensible_records/record_derive.rs | 4 +- .../extensible_records/record_lifetime.rs | 2 +- .../extensible_records/struct_generic.rs | 2 +- .../struct_generic_lifetime.rs | 2 +- .../struct_single_named_field.rs | 4 +- .../struct_single_unnamed_field.rs | 2 +- .../extensible_records/struct_tuple_fields.rs | 2 +- .../struct_two_named_fields.rs | 2 +- .../tests/extensible_records/tuple_record.rs | 2 +- .../tests/extensible_records_tests.rs | 8 +- .../extensible_variants/derive_cgp_data.rs | 6 +- .../derive_cgp_data_empty.rs | 4 +- .../derive_cgp_data_generic.rs | 2 +- .../derive_cgp_data_lifetime.rs | 4 +- .../derive_cgp_data_shape.rs | 4 +- .../extensible_variants/has_fields_enum.rs | 3 +- .../has_fields_enum_generic.rs | 2 +- .../extensible_variants/shape_dispatch.rs | 2 +- .../extensible_variants/shape_dispatch_ref.rs | 2 +- .../tests/extensible_variants/sum_macro.rs | 3 +- .../extensible_variants/variant_dispatch.rs | 2 +- .../tests/extensible_variants_tests.rs | 8 +- .../cgp-tests/tests/field_access/chain.rs | 4 +- .../tests/field_access/chain_deeply_nested.rs | 4 +- .../tests/field_access/chain_inner_life.rs | 2 +- .../tests/field_access/chain_outer_life.rs | 2 +- .../cgp-tests/tests/field_access/index.rs | 4 +- .../tests/field_access/index_display.rs | 2 +- .../tests/field_access/lifetime_field.rs | 2 +- .../cgp-tests/tests/field_access/raw_ident.rs | 4 +- .../cgp-tests/tests/field_access/symbol.rs | 3 +- .../cgp-tests/tests/field_access_tests.rs | 7 +- .../generic_components/component_const.rs | 2 +- .../component_generic_const.rs | 2 +- .../generic_components/component_lifetime.rs | 7 +- .../component_type_param.rs | 4 +- .../generic_components/fn_generic_param.rs | 2 +- .../generic_components/fn_impl_generics.rs | 2 +- .../tests/generic_components_tests.rs | 6 +- .../tests/getters/abstract_type_extend.rs | 5 +- .../tests/getters/abstract_type_use_type.rs | 5 +- .../tests/getters/assoc_type_auto_getter.rs | 2 +- .../tests/getters/assoc_type_getter.rs | 3 +- .../getters/assoc_type_self_referential.rs | 3 +- .../assoc_type_self_referential_auto.rs | 2 +- .../tests/getters/auto_getter_generic.rs | 2 +- crates/tests/cgp-tests/tests/getters/clone.rs | 3 +- .../cgp-tests/tests/getters/clone_auto.rs | 2 +- crates/tests/cgp-tests/tests/getters/mref.rs | 3 +- .../cgp-tests/tests/getters/mref_auto.rs | 2 +- .../cgp-tests/tests/getters/mut_getter.rs | 3 +- .../tests/getters/mut_option_auto.rs | 2 +- .../cgp-tests/tests/getters/mut_slice.rs | 3 +- .../cgp-tests/tests/getters/mut_slice_auto.rs | 2 +- .../cgp-tests/tests/getters/mut_str_auto.rs | 2 +- .../tests/cgp-tests/tests/getters/non_self.rs | 3 +- .../cgp-tests/tests/getters/non_self_auto.rs | 2 +- .../tests/cgp-tests/tests/getters/option.rs | 3 +- .../cgp-tests/tests/getters/option_auto.rs | 2 +- .../tests/getters/option_str_auto.rs | 2 +- crates/tests/cgp-tests/tests/getters/slice.rs | 3 +- .../cgp-tests/tests/getters/slice_auto.rs | 2 +- .../tests/cgp-tests/tests/getters/string.rs | 3 +- .../cgp-tests/tests/getters/string_auto.rs | 2 +- .../tests/getters/string_custom_name.rs | 3 +- .../tests/getters/string_custom_spec.rs | 3 +- crates/tests/cgp-tests/tests/getters_tests.rs | 4 +- .../tests/handlers/computer_macro.rs | 2 +- .../cgp-tests/tests/handlers/handler_macro.rs | 4 +- .../tests/handlers/pipe_computers.rs | 4 +- .../cgp-tests/tests/handlers/pipe_handlers.rs | 4 +- .../tests/handlers/producer_macro.rs | 2 +- .../tests/cgp-tests/tests/handlers_tests.rs | 7 +- .../rectangle_or_circle.rs | 3 +- .../higher_order_providers/scaled_area.rs | 2 +- .../higher_order_providers/use_provider_fn.rs | 3 +- .../use_provider_impl.rs | 4 +- .../tests/higher_order_providers_tests.rs | 4 +- .../tests/impl_side_dependencies/fn_extend.rs | 5 +- .../tests/impl_side_dependencies/fn_uses.rs | 5 +- .../fn_uses_associated_type.rs | 4 +- .../tests/impl_side_dependencies/impl_uses.rs | 5 +- .../impl_uses_associated_type.rs | 4 +- .../tests/impl_side_dependencies_tests.rs | 7 +- .../implicit_arguments/cgp_fn_calling_fn.rs | 2 +- .../cgp_fn_custom_trait_name.rs | 2 +- .../tests/implicit_arguments/cgp_fn_greet.rs | 3 +- .../cgp_fn_multi_and_use_type.rs | 3 +- .../cgp_fn_mut_self_immutable.rs | 3 +- .../implicit_arguments/cgp_fn_mut_slice.rs | 3 +- .../implicit_arguments/cgp_fn_mutable.rs | 3 +- .../implicit_arguments/cgp_fn_option_mut.rs | 3 +- .../implicit_arguments/cgp_fn_option_str.rs | 3 +- .../implicit_arguments/cgp_fn_owned_tuple.rs | 3 +- .../tests/implicit_arguments/cgp_fn_slice.rs | 3 +- .../implicit_arguments/cgp_impl_implicit.rs | 3 +- .../cgp_impl_implicit_generic.rs | 4 +- .../implicit_arguments/implicit_context.rs | 2 +- .../tests/implicit_arguments_tests.rs | 5 +- .../tests/monadic_handlers/err_monadic.rs | 4 +- .../monadic_handlers/ok_err_monadic_trans.rs | 4 +- .../tests/monadic_handlers/ok_monadic.rs | 4 +- .../cgp-tests/tests/monadic_handlers_tests.rs | 4 +- .../tests/namespaces/default_impl_use_type.rs | 4 +- .../tests/namespaces/default_impls.rs | 5 +- .../tests/namespaces/default_impls_wiring.rs | 4 +- .../cgp-tests/tests/namespaces/extended.rs | 3 +- .../namespaces/extended_namespace_wiring.rs | 4 +- .../tests/namespaces/for_where_clause.rs | 4 +- .../tests/namespaces/multi_param_namespace.rs | 4 +- .../tests/namespaces/multi_param_open.rs | 2 +- .../tests/namespaces/namespace_basic.rs | 4 +- .../tests/namespaces/namespace_group.rs | 4 +- .../tests/namespaces/namespace_multi.rs | 4 +- .../tests/namespaces/namespace_symbol_path.rs | 4 +- .../tests/namespaces/namespace_type_path.rs | 4 +- .../tests/namespaces/open_dispatch.rs | 4 +- .../namespaces/prefix_default_namespace.rs | 4 +- .../tests/namespaces/redirect_lookup.rs | 4 +- .../tests/cgp-tests/tests/namespaces_tests.rs | 9 +- docs/AGENTS.md | 109 -- docs/README.md | 33 - docs/communication-strategy/AGENTS.md | 41 - docs/communication-strategy/README.md | 73 -- .../attention-and-engagement.md | 54 - docs/communication-strategy/formats.md | 46 - docs/communication-strategy/glossary.md | 51 - docs/communication-strategy/key-features.md | 27 - docs/communication-strategy/positioning.md | 40 - .../communication-strategy/problems-solved.md | 121 -- .../communication-strategy/reader-profiles.md | 87 -- docs/communication-strategy/selling-points.md | 149 --- docs/communication-strategy/skepticism.md | 123 -- docs/communication-strategy/tag-lines.md | 89 -- .../technical-barriers.md | 74 -- docs/communication-strategy/vocabulary.md | 56 - .../communication-strategy/worked-examples.md | 171 --- docs/concepts/README.md | 30 - docs/concepts/abstract-types.md | 85 -- docs/concepts/aggregate-providers.md | 64 - docs/concepts/check-traits.md | 56 - docs/concepts/coherence.md | 129 -- docs/concepts/consumer-and-provider-traits.md | 73 -- docs/concepts/dispatching.md | 39 - docs/concepts/extensible-records.md | 75 -- docs/concepts/extensible-variants.md | 88 -- docs/concepts/handlers.md | 43 - docs/concepts/higher-order-providers.md | 82 -- docs/concepts/impl-side-dependencies.md | 50 - docs/concepts/implicit-arguments.md | 44 - docs/concepts/modular-error-handling.md | 112 -- docs/concepts/modularity-hierarchy.md | 156 --- docs/concepts/monadic-handlers.md | 47 - docs/concepts/namespaces.md | 89 -- docs/concepts/send-bounds.md | 123 -- docs/concepts/type-level-dsls.md | 97 -- docs/errors/AGENTS.md | 58 - docs/errors/README.md | 88 -- docs/errors/checks/check-trait-failure.md | 103 -- .../checks/higher-order-provider-layer.md | 75 -- docs/errors/checks/ordinary-trait-bound.md | 84 -- .../checks/unregistered-namespace-path.md | 86 -- docs/errors/checks/verbose-cascade.md | 70 -- docs/errors/error_codes/README.md | 40 - docs/errors/error_codes/cargo-cgp-codes.md | 50 - docs/errors/error_codes/e0117.md | 21 - docs/errors/error_codes/e0119.md | 24 - docs/errors/error_codes/e0207.md | 20 - docs/errors/error_codes/e0210.md | 21 - docs/errors/error_codes/e0275.md | 23 - docs/errors/error_codes/e0277.md | 30 - docs/errors/error_codes/e0428.md | 19 - docs/errors/error_codes/e0576.md | 20 - docs/errors/error_codes/e0599.md | 26 - docs/errors/hidden/unsatisfied-dependency.md | 100 -- .../lowering/ill-formed-generated-type.md | 49 - .../lowering/unresolved-imported-type.md | 58 - docs/errors/wiring/conflicting-wiring.md | 81 -- .../wiring/namespace-forwarding-conflict.md | 71 -- .../wiring/namespace-inheritance-cycle.md | 52 - .../wiring/namespace-override-conflict.md | 86 -- docs/errors/wiring/orphan-rule.md | 68 -- docs/errors/wiring/unconstrained-generic.md | 49 - docs/errors/wiring/wiring-cycle.md | 62 - docs/examples/README.md | 23 - docs/examples/application-builder.md | 336 ------ docs/examples/area-calculation.md | 176 --- docs/examples/expression-interpreter.md | 328 ----- docs/examples/extensible-shapes.md | 226 ---- docs/examples/modular-serialization.md | 403 ------- docs/examples/money-transfer-api.md | 450 ------- docs/examples/profile-picture.md | 267 ----- docs/examples/shell-scripting-dsl.md | 261 ---- docs/examples/social-media-app.md | 388 ------ docs/guides/README.md | 43 - docs/guides/capability-supertraits.md | 34 - docs/guides/debugging.md | 112 -- docs/guides/declaring-dependencies.md | 42 - docs/guides/dispatching-per-type.md | 43 - docs/guides/importing-abstract-types.md | 93 -- docs/guides/namespaces-and-prefixes.md | 314 ----- docs/guides/reading-context-fields.md | 46 - docs/guides/writing-providers.md | 70 -- docs/implementation/AGENTS.md | 78 -- docs/implementation/README.md | 141 --- docs/implementation/asts/attributes/README.md | 46 - .../asts/attributes/default_impl.md | 48 - .../asts/attributes/derive_delegate.md | 44 - docs/implementation/asts/attributes/extend.md | 29 - .../asts/attributes/extend_where.md | 20 - .../asts/attributes/use_provider.md | 35 - .../asts/attributes/use_type.md | 87 -- docs/implementation/asts/attributes/uses.md | 34 - docs/implementation/asts/blanket_trait.md | 25 - docs/implementation/asts/cgp_component.md | 38 - docs/implementation/asts/cgp_data.md | 56 - docs/implementation/asts/cgp_fn.md | 41 - docs/implementation/asts/cgp_getter.md | 40 - docs/implementation/asts/cgp_impl.md | 65 - docs/implementation/asts/cgp_provider.md | 67 -- docs/implementation/asts/cgp_type.md | 23 - docs/implementation/asts/check_components.md | 58 - .../implementation/asts/delegate_component.md | 65 - docs/implementation/asts/namespace.md | 47 - docs/implementation/asts/path.md | 46 - docs/implementation/asts/product.md | 38 - docs/implementation/asts/sum.md | 26 - docs/implementation/asts/symbol.md | 30 - .../implementation/entrypoints/async_trait.md | 52 - .../entrypoints/blanket_trait.md | 90 -- .../entrypoints/cgp_auto_dispatch.md | 79 -- .../entrypoints/cgp_auto_getter.md | 95 -- .../entrypoints/cgp_component.md | 99 -- .../entrypoints/cgp_computer.md | 70 -- docs/implementation/entrypoints/cgp_fn.md | 113 -- docs/implementation/entrypoints/cgp_getter.md | 95 -- docs/implementation/entrypoints/cgp_impl.md | 114 -- .../entrypoints/cgp_namespace.md | 140 --- .../entrypoints/cgp_new_provider.md | 44 - .../entrypoints/cgp_producer.md | 55 - .../entrypoints/cgp_provider.md | 80 -- docs/implementation/entrypoints/cgp_type.md | 90 -- .../entrypoints/check_components.md | 87 -- .../delegate_and_check_components.md | 86 -- .../entrypoints/delegate_components.md | 145 --- .../entrypoints/derive_build_field.md | 64 - .../entrypoints/derive_cgp_data.md | 76 -- .../entrypoints/derive_cgp_record.md | 32 - .../entrypoints/derive_cgp_variant.md | 35 - .../entrypoints/derive_extract_field.md | 74 -- .../entrypoints/derive_from_variant.md | 56 - .../entrypoints/derive_has_field.md | 83 -- .../entrypoints/derive_has_fields.md | 79 -- docs/implementation/entrypoints/path.md | 52 - docs/implementation/entrypoints/product.md | 62 - .../entrypoints/snapshot_macros.md | 81 -- docs/implementation/entrypoints/sum.md | 47 - docs/implementation/entrypoints/symbol.md | 54 - .../functions/derive/delegated_impls.md | 32 - .../functions/derive/generics.md | 13 - .../implementation/functions/derive/idents.md | 15 - .../functions/parse/is_provider_params.md | 24 - docs/implementation/macros/define_keyword.md | 18 - .../macros/export_constructs.md | 16 - docs/implementation/macros/parse_internal.md | 23 - docs/reference/README.md | 203 ---- docs/reference/attributes/derive_delegate.md | 154 --- docs/reference/attributes/extend.md | 127 -- docs/reference/attributes/extend_where.md | 67 -- docs/reference/attributes/implicit.md | 121 -- docs/reference/attributes/use_provider.md | 133 --- docs/reference/attributes/use_type.md | 214 ---- docs/reference/attributes/uses.md | 111 -- docs/reference/cargo-cgp.md | 151 --- docs/reference/components/can_raise_error.md | 84 -- docs/reference/components/computer.md | 114 -- docs/reference/components/handler.md | 90 -- docs/reference/components/has_error_type.md | 75 -- docs/reference/components/has_runtime.md | 94 -- docs/reference/components/has_type.md | 78 -- docs/reference/components/producer.md | 87 -- docs/reference/components/runner.md | 107 -- docs/reference/components/try_computer.md | 101 -- docs/reference/derives/derive_build_field.md | 135 --- docs/reference/derives/derive_cgp_data.md | 203 ---- docs/reference/derives/derive_cgp_record.md | 140 --- docs/reference/derives/derive_cgp_variant.md | 126 -- .../reference/derives/derive_extract_field.md | 152 --- docs/reference/derives/derive_from_variant.md | 92 -- docs/reference/derives/derive_has_field.md | 181 --- docs/reference/derives/derive_has_fields.md | 155 --- docs/reference/macros/async_trait.md | 141 --- docs/reference/macros/blanket_trait.md | 173 --- docs/reference/macros/cgp_auto_dispatch.md | 140 --- docs/reference/macros/cgp_auto_getter.md | 166 --- docs/reference/macros/cgp_component.md | 193 --- docs/reference/macros/cgp_computer.md | 151 --- docs/reference/macros/cgp_fn.md | 176 --- docs/reference/macros/cgp_getter.md | 156 --- docs/reference/macros/cgp_impl.md | 185 --- docs/reference/macros/cgp_namespace.md | 225 ---- docs/reference/macros/cgp_new_provider.md | 120 -- docs/reference/macros/cgp_producer.md | 123 -- docs/reference/macros/cgp_provider.md | 144 --- docs/reference/macros/cgp_type.md | 134 --- docs/reference/macros/check_components.md | 218 ---- .../macros/delegate_and_check_components.md | 190 --- docs/reference/macros/delegate_components.md | 259 ---- docs/reference/macros/path.md | 99 -- docs/reference/macros/product.md | 108 -- docs/reference/macros/sum.md | 94 -- docs/reference/macros/symbol.md | 90 -- docs/reference/providers/chain_getters.md | 103 -- .../providers/dispatch_combinators.md | 270 ----- docs/reference/providers/error_providers.md | 194 --- .../providers/handler_combinators.md | 221 ---- docs/reference/providers/monad_providers.md | 105 -- docs/reference/providers/redirect_lookup.md | 84 -- docs/reference/providers/use_context.md | 90 -- docs/reference/providers/use_default.md | 111 -- docs/reference/providers/use_delegate.md | 101 -- .../reference/providers/use_delegated_type.md | 92 -- docs/reference/providers/use_field.md | 97 -- docs/reference/providers/use_field_ref.md | 88 -- docs/reference/providers/use_fields.md | 91 -- docs/reference/providers/use_type.md | 102 -- docs/reference/providers/with_provider.md | 99 -- docs/reference/traits/can_use_component.md | 100 -- docs/reference/traits/cast.md | 110 -- docs/reference/traits/default_namespace.md | 108 -- docs/reference/traits/delegate_component.md | 114 -- docs/reference/traits/extract_field.md | 125 -- docs/reference/traits/from_variant.md | 76 -- docs/reference/traits/has_builder.md | 122 -- docs/reference/traits/has_field.md | 134 --- docs/reference/traits/has_fields.md | 90 -- docs/reference/traits/is_provider_for.md | 108 -- docs/reference/traits/map_type.md | 113 -- docs/reference/traits/monad.md | 72 -- docs/reference/traits/optional_fields.md | 249 ---- docs/reference/traits/product_ops.md | 113 -- docs/reference/traits/static_format.md | 97 -- docs/reference/types/chars.md | 77 -- docs/reference/types/cons.md | 75 -- docs/reference/types/either.md | 84 -- docs/reference/types/field.md | 75 -- docs/reference/types/index.md | 72 -- docs/reference/types/life.md | 59 - docs/reference/types/mref.md | 61 - docs/reference/types/path_cons.md | 84 -- docs/related-work/AGENTS.md | 51 - docs/related-work/README.md | 28 - docs/related-work/algebraic-effects.md | 209 ---- docs/related-work/dependency-injection.md | 194 --- docs/related-work/dynamic-dispatch.md | 164 --- docs/related-work/implicit-parameters.md | 165 --- docs/related-work/ml-modules.md | 189 --- docs/related-work/reflection.md | 249 ---- docs/related-work/row-polymorphism.md | 182 --- docs/related-work/type-classes.md | 173 --- docs/skills/README.md | 13 - docs/skills/cgp/SKILL.md | 1051 ----------------- docs/skills/cgp/references/abstract-types.md | 162 --- docs/skills/cgp/references/checking.md | 207 ---- docs/skills/cgp/references/components.md | 189 --- .../skills/cgp/references/error-extraction.md | 73 -- docs/skills/cgp/references/error-handling.md | 135 --- docs/skills/cgp/references/extensible-data.md | 147 --- .../cgp/references/functions-and-getters.md | 301 ----- docs/skills/cgp/references/handlers.md | 250 ---- .../cgp/references/higher-order-providers.md | 150 --- docs/skills/cgp/references/macro-grammar.md | 323 ----- docs/skills/cgp/references/modern-idioms.md | 284 ----- .../cgp/references/modularity-hierarchy.md | 162 --- docs/skills/cgp/references/namespaces.md | 167 --- .../cgp/references/type-level-primitives.md | 197 --- docs/skills/cgp/references/wiring.md | 191 --- sibling-projects.md | 43 + 508 files changed, 573 insertions(+), 28494 deletions(-) delete mode 100644 docs/AGENTS.md delete mode 100644 docs/README.md delete mode 100644 docs/communication-strategy/AGENTS.md delete mode 100644 docs/communication-strategy/README.md delete mode 100644 docs/communication-strategy/attention-and-engagement.md delete mode 100644 docs/communication-strategy/formats.md delete mode 100644 docs/communication-strategy/glossary.md delete mode 100644 docs/communication-strategy/key-features.md delete mode 100644 docs/communication-strategy/positioning.md delete mode 100644 docs/communication-strategy/problems-solved.md delete mode 100644 docs/communication-strategy/reader-profiles.md delete mode 100644 docs/communication-strategy/selling-points.md delete mode 100644 docs/communication-strategy/skepticism.md delete mode 100644 docs/communication-strategy/tag-lines.md delete mode 100644 docs/communication-strategy/technical-barriers.md delete mode 100644 docs/communication-strategy/vocabulary.md delete mode 100644 docs/communication-strategy/worked-examples.md delete mode 100644 docs/concepts/README.md delete mode 100644 docs/concepts/abstract-types.md delete mode 100644 docs/concepts/aggregate-providers.md delete mode 100644 docs/concepts/check-traits.md delete mode 100644 docs/concepts/coherence.md delete mode 100644 docs/concepts/consumer-and-provider-traits.md delete mode 100644 docs/concepts/dispatching.md delete mode 100644 docs/concepts/extensible-records.md delete mode 100644 docs/concepts/extensible-variants.md delete mode 100644 docs/concepts/handlers.md delete mode 100644 docs/concepts/higher-order-providers.md delete mode 100644 docs/concepts/impl-side-dependencies.md delete mode 100644 docs/concepts/implicit-arguments.md delete mode 100644 docs/concepts/modular-error-handling.md delete mode 100644 docs/concepts/modularity-hierarchy.md delete mode 100644 docs/concepts/monadic-handlers.md delete mode 100644 docs/concepts/namespaces.md delete mode 100644 docs/concepts/send-bounds.md delete mode 100644 docs/concepts/type-level-dsls.md delete mode 100644 docs/errors/AGENTS.md delete mode 100644 docs/errors/README.md delete mode 100644 docs/errors/checks/check-trait-failure.md delete mode 100644 docs/errors/checks/higher-order-provider-layer.md delete mode 100644 docs/errors/checks/ordinary-trait-bound.md delete mode 100644 docs/errors/checks/unregistered-namespace-path.md delete mode 100644 docs/errors/checks/verbose-cascade.md delete mode 100644 docs/errors/error_codes/README.md delete mode 100644 docs/errors/error_codes/cargo-cgp-codes.md delete mode 100644 docs/errors/error_codes/e0117.md delete mode 100644 docs/errors/error_codes/e0119.md delete mode 100644 docs/errors/error_codes/e0207.md delete mode 100644 docs/errors/error_codes/e0210.md delete mode 100644 docs/errors/error_codes/e0275.md delete mode 100644 docs/errors/error_codes/e0277.md delete mode 100644 docs/errors/error_codes/e0428.md delete mode 100644 docs/errors/error_codes/e0576.md delete mode 100644 docs/errors/error_codes/e0599.md delete mode 100644 docs/errors/hidden/unsatisfied-dependency.md delete mode 100644 docs/errors/lowering/ill-formed-generated-type.md delete mode 100644 docs/errors/lowering/unresolved-imported-type.md delete mode 100644 docs/errors/wiring/conflicting-wiring.md delete mode 100644 docs/errors/wiring/namespace-forwarding-conflict.md delete mode 100644 docs/errors/wiring/namespace-inheritance-cycle.md delete mode 100644 docs/errors/wiring/namespace-override-conflict.md delete mode 100644 docs/errors/wiring/orphan-rule.md delete mode 100644 docs/errors/wiring/unconstrained-generic.md delete mode 100644 docs/errors/wiring/wiring-cycle.md delete mode 100644 docs/examples/README.md delete mode 100644 docs/examples/application-builder.md delete mode 100644 docs/examples/area-calculation.md delete mode 100644 docs/examples/expression-interpreter.md delete mode 100644 docs/examples/extensible-shapes.md delete mode 100644 docs/examples/modular-serialization.md delete mode 100644 docs/examples/money-transfer-api.md delete mode 100644 docs/examples/profile-picture.md delete mode 100644 docs/examples/shell-scripting-dsl.md delete mode 100644 docs/examples/social-media-app.md delete mode 100644 docs/guides/README.md delete mode 100644 docs/guides/capability-supertraits.md delete mode 100644 docs/guides/debugging.md delete mode 100644 docs/guides/declaring-dependencies.md delete mode 100644 docs/guides/dispatching-per-type.md delete mode 100644 docs/guides/importing-abstract-types.md delete mode 100644 docs/guides/namespaces-and-prefixes.md delete mode 100644 docs/guides/reading-context-fields.md delete mode 100644 docs/guides/writing-providers.md delete mode 100644 docs/implementation/AGENTS.md delete mode 100644 docs/implementation/README.md delete mode 100644 docs/implementation/asts/attributes/README.md delete mode 100644 docs/implementation/asts/attributes/default_impl.md delete mode 100644 docs/implementation/asts/attributes/derive_delegate.md delete mode 100644 docs/implementation/asts/attributes/extend.md delete mode 100644 docs/implementation/asts/attributes/extend_where.md delete mode 100644 docs/implementation/asts/attributes/use_provider.md delete mode 100644 docs/implementation/asts/attributes/use_type.md delete mode 100644 docs/implementation/asts/attributes/uses.md delete mode 100644 docs/implementation/asts/blanket_trait.md delete mode 100644 docs/implementation/asts/cgp_component.md delete mode 100644 docs/implementation/asts/cgp_data.md delete mode 100644 docs/implementation/asts/cgp_fn.md delete mode 100644 docs/implementation/asts/cgp_getter.md delete mode 100644 docs/implementation/asts/cgp_impl.md delete mode 100644 docs/implementation/asts/cgp_provider.md delete mode 100644 docs/implementation/asts/cgp_type.md delete mode 100644 docs/implementation/asts/check_components.md delete mode 100644 docs/implementation/asts/delegate_component.md delete mode 100644 docs/implementation/asts/namespace.md delete mode 100644 docs/implementation/asts/path.md delete mode 100644 docs/implementation/asts/product.md delete mode 100644 docs/implementation/asts/sum.md delete mode 100644 docs/implementation/asts/symbol.md delete mode 100644 docs/implementation/entrypoints/async_trait.md delete mode 100644 docs/implementation/entrypoints/blanket_trait.md delete mode 100644 docs/implementation/entrypoints/cgp_auto_dispatch.md delete mode 100644 docs/implementation/entrypoints/cgp_auto_getter.md delete mode 100644 docs/implementation/entrypoints/cgp_component.md delete mode 100644 docs/implementation/entrypoints/cgp_computer.md delete mode 100644 docs/implementation/entrypoints/cgp_fn.md delete mode 100644 docs/implementation/entrypoints/cgp_getter.md delete mode 100644 docs/implementation/entrypoints/cgp_impl.md delete mode 100644 docs/implementation/entrypoints/cgp_namespace.md delete mode 100644 docs/implementation/entrypoints/cgp_new_provider.md delete mode 100644 docs/implementation/entrypoints/cgp_producer.md delete mode 100644 docs/implementation/entrypoints/cgp_provider.md delete mode 100644 docs/implementation/entrypoints/cgp_type.md delete mode 100644 docs/implementation/entrypoints/check_components.md delete mode 100644 docs/implementation/entrypoints/delegate_and_check_components.md delete mode 100644 docs/implementation/entrypoints/delegate_components.md delete mode 100644 docs/implementation/entrypoints/derive_build_field.md delete mode 100644 docs/implementation/entrypoints/derive_cgp_data.md delete mode 100644 docs/implementation/entrypoints/derive_cgp_record.md delete mode 100644 docs/implementation/entrypoints/derive_cgp_variant.md delete mode 100644 docs/implementation/entrypoints/derive_extract_field.md delete mode 100644 docs/implementation/entrypoints/derive_from_variant.md delete mode 100644 docs/implementation/entrypoints/derive_has_field.md delete mode 100644 docs/implementation/entrypoints/derive_has_fields.md delete mode 100644 docs/implementation/entrypoints/path.md delete mode 100644 docs/implementation/entrypoints/product.md delete mode 100644 docs/implementation/entrypoints/snapshot_macros.md delete mode 100644 docs/implementation/entrypoints/sum.md delete mode 100644 docs/implementation/entrypoints/symbol.md delete mode 100644 docs/implementation/functions/derive/delegated_impls.md delete mode 100644 docs/implementation/functions/derive/generics.md delete mode 100644 docs/implementation/functions/derive/idents.md delete mode 100644 docs/implementation/functions/parse/is_provider_params.md delete mode 100644 docs/implementation/macros/define_keyword.md delete mode 100644 docs/implementation/macros/export_constructs.md delete mode 100644 docs/implementation/macros/parse_internal.md delete mode 100644 docs/reference/README.md delete mode 100644 docs/reference/attributes/derive_delegate.md delete mode 100644 docs/reference/attributes/extend.md delete mode 100644 docs/reference/attributes/extend_where.md delete mode 100644 docs/reference/attributes/implicit.md delete mode 100644 docs/reference/attributes/use_provider.md delete mode 100644 docs/reference/attributes/use_type.md delete mode 100644 docs/reference/attributes/uses.md delete mode 100644 docs/reference/cargo-cgp.md delete mode 100644 docs/reference/components/can_raise_error.md delete mode 100644 docs/reference/components/computer.md delete mode 100644 docs/reference/components/handler.md delete mode 100644 docs/reference/components/has_error_type.md delete mode 100644 docs/reference/components/has_runtime.md delete mode 100644 docs/reference/components/has_type.md delete mode 100644 docs/reference/components/producer.md delete mode 100644 docs/reference/components/runner.md delete mode 100644 docs/reference/components/try_computer.md delete mode 100644 docs/reference/derives/derive_build_field.md delete mode 100644 docs/reference/derives/derive_cgp_data.md delete mode 100644 docs/reference/derives/derive_cgp_record.md delete mode 100644 docs/reference/derives/derive_cgp_variant.md delete mode 100644 docs/reference/derives/derive_extract_field.md delete mode 100644 docs/reference/derives/derive_from_variant.md delete mode 100644 docs/reference/derives/derive_has_field.md delete mode 100644 docs/reference/derives/derive_has_fields.md delete mode 100644 docs/reference/macros/async_trait.md delete mode 100644 docs/reference/macros/blanket_trait.md delete mode 100644 docs/reference/macros/cgp_auto_dispatch.md delete mode 100644 docs/reference/macros/cgp_auto_getter.md delete mode 100644 docs/reference/macros/cgp_component.md delete mode 100644 docs/reference/macros/cgp_computer.md delete mode 100644 docs/reference/macros/cgp_fn.md delete mode 100644 docs/reference/macros/cgp_getter.md delete mode 100644 docs/reference/macros/cgp_impl.md delete mode 100644 docs/reference/macros/cgp_namespace.md delete mode 100644 docs/reference/macros/cgp_new_provider.md delete mode 100644 docs/reference/macros/cgp_producer.md delete mode 100644 docs/reference/macros/cgp_provider.md delete mode 100644 docs/reference/macros/cgp_type.md delete mode 100644 docs/reference/macros/check_components.md delete mode 100644 docs/reference/macros/delegate_and_check_components.md delete mode 100644 docs/reference/macros/delegate_components.md delete mode 100644 docs/reference/macros/path.md delete mode 100644 docs/reference/macros/product.md delete mode 100644 docs/reference/macros/sum.md delete mode 100644 docs/reference/macros/symbol.md delete mode 100644 docs/reference/providers/chain_getters.md delete mode 100644 docs/reference/providers/dispatch_combinators.md delete mode 100644 docs/reference/providers/error_providers.md delete mode 100644 docs/reference/providers/handler_combinators.md delete mode 100644 docs/reference/providers/monad_providers.md delete mode 100644 docs/reference/providers/redirect_lookup.md delete mode 100644 docs/reference/providers/use_context.md delete mode 100644 docs/reference/providers/use_default.md delete mode 100644 docs/reference/providers/use_delegate.md delete mode 100644 docs/reference/providers/use_delegated_type.md delete mode 100644 docs/reference/providers/use_field.md delete mode 100644 docs/reference/providers/use_field_ref.md delete mode 100644 docs/reference/providers/use_fields.md delete mode 100644 docs/reference/providers/use_type.md delete mode 100644 docs/reference/providers/with_provider.md delete mode 100644 docs/reference/traits/can_use_component.md delete mode 100644 docs/reference/traits/cast.md delete mode 100644 docs/reference/traits/default_namespace.md delete mode 100644 docs/reference/traits/delegate_component.md delete mode 100644 docs/reference/traits/extract_field.md delete mode 100644 docs/reference/traits/from_variant.md delete mode 100644 docs/reference/traits/has_builder.md delete mode 100644 docs/reference/traits/has_field.md delete mode 100644 docs/reference/traits/has_fields.md delete mode 100644 docs/reference/traits/is_provider_for.md delete mode 100644 docs/reference/traits/map_type.md delete mode 100644 docs/reference/traits/monad.md delete mode 100644 docs/reference/traits/optional_fields.md delete mode 100644 docs/reference/traits/product_ops.md delete mode 100644 docs/reference/traits/static_format.md delete mode 100644 docs/reference/types/chars.md delete mode 100644 docs/reference/types/cons.md delete mode 100644 docs/reference/types/either.md delete mode 100644 docs/reference/types/field.md delete mode 100644 docs/reference/types/index.md delete mode 100644 docs/reference/types/life.md delete mode 100644 docs/reference/types/mref.md delete mode 100644 docs/reference/types/path_cons.md delete mode 100644 docs/related-work/AGENTS.md delete mode 100644 docs/related-work/README.md delete mode 100644 docs/related-work/algebraic-effects.md delete mode 100644 docs/related-work/dependency-injection.md delete mode 100644 docs/related-work/dynamic-dispatch.md delete mode 100644 docs/related-work/implicit-parameters.md delete mode 100644 docs/related-work/ml-modules.md delete mode 100644 docs/related-work/reflection.md delete mode 100644 docs/related-work/row-polymorphism.md delete mode 100644 docs/related-work/type-classes.md delete mode 100644 docs/skills/README.md delete mode 100644 docs/skills/cgp/SKILL.md delete mode 100644 docs/skills/cgp/references/abstract-types.md delete mode 100644 docs/skills/cgp/references/checking.md delete mode 100644 docs/skills/cgp/references/components.md delete mode 100644 docs/skills/cgp/references/error-extraction.md delete mode 100644 docs/skills/cgp/references/error-handling.md delete mode 100644 docs/skills/cgp/references/extensible-data.md delete mode 100644 docs/skills/cgp/references/functions-and-getters.md delete mode 100644 docs/skills/cgp/references/handlers.md delete mode 100644 docs/skills/cgp/references/higher-order-providers.md delete mode 100644 docs/skills/cgp/references/macro-grammar.md delete mode 100644 docs/skills/cgp/references/modern-idioms.md delete mode 100644 docs/skills/cgp/references/modularity-hierarchy.md delete mode 100644 docs/skills/cgp/references/namespaces.md delete mode 100644 docs/skills/cgp/references/type-level-primitives.md delete mode 100644 docs/skills/cgp/references/wiring.md create mode 100644 sibling-projects.md diff --git a/.claude/skills/cgp b/.claude/skills/cgp index bf705a63..573d43d5 120000 --- a/.claude/skills/cgp +++ b/.claude/skills/cgp @@ -1 +1 @@ -../../docs/skills/cgp \ No newline at end of file +../../../cgp-skills/cgp \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 7d5cc68c..5aa264a6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,9 +5,9 @@ This file provides guidance to LLM agents when working with code in this reposit ## Project status This working tree **is** the upcoming **v0.8.0** release. Every crate under [crates/](crates) is -versioned `0.8.0-alpha` in its `Cargo.toml` — the pre-release of v0.8.0 — and the documentation -under [docs/](docs) already refers to the current version as v0.8.0, so nothing needs a version edit -when v0.8.0 ships. +versioned `0.8.0-alpha` in its `Cargo.toml` — the pre-release of v0.8.0 — and the documentation in the +[knowledge base](https://github.com/contextgeneric/cgp-knowledge-base/tree/main/cgp) already refers to +the current version as v0.8.0, so nothing needs a version edit when v0.8.0 ships. The stable release on crates.io is still **v0.7.0**, and it is not compatible with this tree. v0.7.0 carries legacy features and syntax that v0.8.0 has removed or changed, so code or documentation @@ -18,62 +18,78 @@ Only the library crates are published. The five crates under [crates/tests/](cra marked `publish = false`, so `cargo publish --workspace` skips them and publishes just the real CGP crates. -## The two projects: cgp and cargo-cgp - -`cgp` and `cargo-cgp` are developed together but live in separate Git repositories, so a task in one -routinely reads — and sometimes updates — the other. [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) -is CGP's **first-class toolchain**: a cargo subcommand that rewrites the compiler's diagnostics into -readable, root-cause-first CGP errors. It is the recommended way to build and check CGP code and to -read a CGP compile error, so recommend it wherever CGP errors are discussed, and prefer it over raw -`cargo check` when diagnosing a wiring failure in this repository. - -**Finding `cargo-cgp`.** Look first in the parent directory, at `../cargo-cgp`. When that sibling -checkout is present, use it — it is the fastest reference and reflects any uncommitted work. When it -is absent, fall back to the official repository at -[github.com/contextgeneric/cargo-cgp](https://github.com/contextgeneric/cargo-cgp), fetching the file -you need from there. (The mirror image applies to an agent working in `cargo-cgp` and looking for -`cgp` at `../cgp`.) - -**Writing cross-project links.** A *link* from a committed file here to a document in `cargo-cgp` — a -Markdown link or a URL — is **always written as a GitHub URL** -(`https://github.com/contextgeneric/cargo-cgp/blob/main/`), never as a relative `../cargo-cgp/...` -link, so the document resolves for a reader who has only this repository checked out. When *reading* -such a link yourself, prefer the local `../cargo-cgp` checkout if it is available and fetch the GitHub -URL only when it is not. (A bare mention of the checkout's location, like the path `../cargo-cgp`, is -a filesystem reference rather than a link and stays relative.) - -**Keeping the two in sync.** When a change here affects `cargo-cgp` — most often when a construct -change alters a compiler diagnostic that `cargo-cgp` has a UI fixture for — and `cargo-cgp` is checked -out at `../cargo-cgp`, update it in the same change, following `cargo-cgp`'s own conventions (its -`AGENTS.md`). When `../cargo-cgp` is not checked out, state plainly what needs updating there so it is -not silently left to drift. The post-codegen compile-fail cases this repository used to keep in -`cgp-compile-fail-tests` now live as `cargo-cgp` UI fixtures, and the [error catalog](docs/errors/README.md) -links each error class to the fixture that backs it. +## The sibling projects + +`cgp` is one repository in a family that is developed together, so a task here routinely reads — and +often updates — another one. [sibling-projects.md](sibling-projects.md) is the authoritative list: +where each project lives, which revision of it to read, and the rules for finding a sibling locally +versus linking to it. Three of them matter constantly. + +**This repository's documentation lives in [`cgp-knowledge-base`](https://github.com/contextgeneric/cgp-knowledge-base).** +There is no `docs/` directory here. The construct reference, the concepts, the guides, the error +catalog, and the macro-implementation notes are all in that repository's +[`cgp/`](https://github.com/contextgeneric/cgp-knowledge-base/tree/main/cgp) directory, and the worked +examples sit at its top level. It is a *consolidated* base — `cargo-cgp`'s documentation is the +neighbouring section — which is why an error class here and the tool's handling of it are now one +directory apart. Treat the knowledge base as part of this repository's surface: **a change to a +construct's behavior carries its documentation change**, and when the checkout is missing, say plainly +what needs updating there. + +**[`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) is CGP's first-class toolchain**: a cargo +subcommand that rewrites the compiler's diagnostics into readable, root-cause-first CGP errors. It is +the recommended way to build and check CGP code and to read a CGP compile error, so recommend it +wherever CGP errors are discussed, and prefer it over raw `cargo check` when diagnosing a wiring +failure here. When a construct change alters a diagnostic it has a UI fixture for, update the fixture +in the same change, following that project's own conventions. + +**[`cgp-skills`](https://github.com/contextgeneric/cgp-skills) holds the `/cgp` skill**, the distilled +view of the knowledge base that every agent loads first. It is the easiest view to leave stale, so a +change to a construct's syntax, expansion, defaults, or recommended form propagates out to the matching +sub-skill in the same change. + +**Finding a sibling.** Look first in the parent directory, at `../cargo-cgp`, +`../cgp-knowledge-base`, `../cgp-skills`. When the checkout is present, use it — it is the fastest +reference and reflects any uncommitted work. When it is absent, fetch the file you need from the +project's repository at the revision [sibling-projects.md](sibling-projects.md) records. + +**Writing cross-project links.** A *link* from a committed file here to a document in another +project — a Markdown link or a URL — is **always written as a GitHub URL** on `main` +(`https://github.com/contextgeneric//blob/main/`), never as a relative +`../cargo-cgp/...` link, so it resolves for a reader who has only this repository checked out. When +*reading* such a link yourself, prefer the local checkout. A bare mention of a checkout's location, +like the path `../cargo-cgp`, is a filesystem reference rather than a link and stays relative — as is +a doc pointer in a source comment, which names its knowledge-base path +(`cgp-knowledge-base/cgp/implementation/entrypoints/cgp_impl.md`). **The code dependency stays one-way.** No `cgp` *crate* may depend on a `cargo-cgp` crate: -`cargo-cgp` reads `cgp`, never the reverse. Documentation is the deliberate exception — this -repository's docs and skill reference `cargo-cgp` as the recommended toolchain — so it is the *code* -graph that stays acyclic, not the documentation. +`cargo-cgp` reads `cgp`, never the reverse. Documentation is the deliberate exception — the knowledge +base and the skill both reference `cargo-cgp` as the recommended toolchain — so it is the *code* graph +that stays acyclic, not the documentation. The post-codegen compile-fail cases this repository used to +keep in `cgp-compile-fail-tests` now live as `cargo-cgp` UI fixtures, and the +[error catalog](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/errors/README.md) +links each error class to the fixture that backs it. ## Orient before any task This repository **is** the implementation of Context-Generic Programming (CGP), and its behavior is -recorded as much in the knowledge base under [docs/](docs) as in the code. Before starting any task -here — reading, writing, reviewing, debugging, or answering a question — load the CGP mental model -and the documentation that covers what you are about to touch. The following steps are standing -requirements: they apply to every task regardless of how small it looks, not just to the macro -review workflow below. +recorded as much in the [knowledge base](https://github.com/contextgeneric/cgp-knowledge-base/tree/main/cgp) +as in the code. Before starting any task here — reading, writing, reviewing, debugging, or answering a +question — load the CGP mental model and the documentation that covers what you are about to touch. +The following steps are standing requirements: they apply to every task regardless of how small it +looks, not just to the macro review workflow below. Prefer the local `../cgp-knowledge-base` checkout +when reading them, and note that its [summary.md](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/summary.md) +lists every document in one page, which is the fastest way to find the few a task needs. - **Always invoke the `/cgp` skill** to load the fundamentals (consumer vs. provider traits, `#[cgp_component]`/`#[cgp_impl]`/`#[cgp_fn]`, `delegate_components!`, `HasField`, `UseDelegate`, check traits, and so on). Re-invoke it whenever you move into an unfamiliar construct — the macros and core traits here are the ground truth the skill describes, so read the two together. -- **Always read [docs/README.md](docs/README.md)** to orient in the knowledge base, then follow it +- **Always read [cgp-knowledge-base/cgp/README.md](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/README.md)** to orient in the knowledge base, then follow it into the README of whichever section covers your task. -- **Read [docs/reference/README.md](docs/reference/README.md) and the relevant reference documents +- **Read [cgp-knowledge-base/cgp/reference/README.md](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/reference/README.md) and the relevant reference documents whenever the task requires understanding a CGP construct** — what it means, what syntax it accepts, and what code it expands to. -- **Read [docs/implementation/README.md](docs/implementation/README.md) and the relevant +- **Read [cgp-knowledge-base/cgp/implementation/README.md](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/README.md) and the relevant implementation documents whenever the task involves reading or modifying the CGP source code** — they map each macro to its `cgp-macro-core`/`cgp-macro-lib` internals, corner cases, and tests. - **Load the `/dual-reader-prose` skill whenever the task involves editing markdown documentation or @@ -102,7 +118,7 @@ do). standard test harness, e.g. `cargo test -p cgp-tests --test component` - **Post-codegen compile failures** (input a macro accepts but whose expansion fails to compile) are no longer tested here: they are UI fixtures in [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), - the first-class error toolchain, and cataloged in the [error catalog](docs/errors/README.md). See + the first-class error toolchain, and cataloged in the [error catalog](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/errors/README.md). See [crates/tests/AGENTS.md](crates/tests/AGENTS.md) for the workflow. - Many "tests" are **compile-time wiring checks** (`check_components!` / `delegate_and_check_components!`) and **macro-expansion snapshots** — for these, a successful @@ -171,12 +187,12 @@ as the behavior allows. ### Orient before touching anything Perform the standing steps in [Orient before any task](#orient-before-any-task) first, every -iteration. Then read the documentation specific to the macro under review, in [docs/](docs): its -reference document under [docs/reference/](docs/reference), its implementation documents under -[docs/implementation/](docs/implementation) (the `entrypoints/` document, the `asts/` stack it +iteration. Then read the documentation specific to the macro under review, in the [knowledge base](https://github.com/contextgeneric/cgp-knowledge-base/tree/main/cgp): its +reference document under [cgp-knowledge-base/cgp/reference/](https://github.com/contextgeneric/cgp-knowledge-base/tree/main/cgp/reference), its implementation documents under +[cgp-knowledge-base/cgp/implementation/](https://github.com/contextgeneric/cgp-knowledge-base/tree/main/cgp/implementation) (the `entrypoints/` document, the `asts/` stack it drives, and any `functions/` helpers it relies on), and the governing `AGENTS.md` files that define -how those documents stay in sync with the code: [docs/AGENTS.md](docs/AGENTS.md), -[docs/implementation/AGENTS.md](docs/implementation/AGENTS.md), and +how those documents stay in sync with the code: [cgp-knowledge-base/cgp/AGENTS.md](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/AGENTS.md), +[cgp-knowledge-base/cgp/implementation/AGENTS.md](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/AGENTS.md), and [crates/macros/cgp-macro-core/AGENTS.md](crates/macros/cgp-macro-core/AGENTS.md). These establish that the source is the single source of truth and that reference, implementation, snapshot, and skill are four views of it that must never drift. @@ -219,7 +235,7 @@ below is a latent miscompilation — or a broken editor experience — waiting f **Apply every area to every macro you review**, not only the ones that already have a fixture or a worked example for it; these concerns recur across the whole suite, and a macro that has never been audited for one of them (spans especially) most likely has the bug. The areas below are the *what to -check*; the [cross-cutting implementation notes](docs/implementation/README.md#cross-cutting-implementation-notes) +check*; the [cross-cutting implementation notes](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/README.md#cross-cutting-implementation-notes) explain *why the code behaves this way*, and most areas link out to the note or process doc that carries their full detail. @@ -233,7 +249,7 @@ accepted for one attribute while rejected for another). #### Parsing the input Build every `syn` node from quasi-quoted tokens with -[`parse_internal!`](docs/implementation/macros/parse_internal.md) rather than `parse2`/`parse_quote!`, +[`parse_internal!`](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/macros/parse_internal.md) rather than `parse2`/`parse_quote!`, and thread `syn::Result` so a malformed fragment propagates a named error instead of panicking; reserve the panicking `parse_quote!` for tokens trivially guaranteed to parse and `parse2` for re-parsing tokens already known valid. Reject malformed or unsupported input early, at the macro's @@ -241,7 +257,7 @@ own parse stage with a spanned message, rather than letting it surface deep insi Walk the full input space — path-qualified types, generics, lifetimes, arrays, tuples, turbofish, associated-type bindings — and confirm none reaches a parser that fails obscurely; since `syn` parses far more leniently than Rust accepts, validate against CGP's restricted argument types. See -[Parsing](docs/implementation/README.md#parsing-build-with-parse_internal-and-distrust-syns-leniency). +[Parsing](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/README.md#parsing-build-with-parse_internal-and-distrust-syns-leniency). #### Expanding the output @@ -250,9 +266,9 @@ Rust — no conflicting `impl` blocks from a cartesian expansion, no unbound or no empty expansion that checks nothing, no clash on a generated identifier. When an expansion fails to compile, capture it as a [`cargo-cgp` UI fixture](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md) (post-codegen compile-fail cases live there now, filed by the quality of the output the tool produces) -and catalog the class in the [error catalog](docs/errors/README.md); when the failure is a CGP *defect* +and catalog the class in the [error catalog](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/errors/README.md); when the failure is a CGP *defect* the macro should have rejected, also record it under the macro's `## Known issues`, per -[crates/tests/AGENTS.md](crates/tests/AGENTS.md) and [docs/implementation/AGENTS.md](docs/implementation/AGENTS.md). +[crates/tests/AGENTS.md](crates/tests/AGENTS.md) and [cgp-knowledge-base/cgp/implementation/AGENTS.md](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/AGENTS.md). #### Generics @@ -260,8 +276,8 @@ Keep the *kinds* (lifetime, type, const) and the *roles* (impl generics `impl generics, the `` in `Foo`) distinct, render each in the right position, merge parameters from different sources without colliding, and bind every parameter that appears in a generated header so nothing is left free. See -[Generic-parameter insertion](docs/implementation/README.md#generic-parameter-insertion-and-lifetime-ordering) -and [keeping the kinds distinct](docs/implementation/README.md#generics-keep-the-kinds-and-roles-distinct). +[Generic-parameter insertion](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/README.md#generic-parameter-insertion-and-lifetime-ordering) +and [keeping the kinds distinct](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/README.md#generics-keep-the-kinds-and-roles-distinct). #### Qualified paths and hygiene @@ -270,7 +286,7 @@ never as a bare or hand-written path — grep the codegen for any CGP name not i `exports` marker. Give the reserved identifiers the expansion introduces the double-underscore form (`__Context__`, `__Provider__`, `__Component__`, …) so they cannot clash with a user's names, and keep the expansion idempotent when the same entry is listed more than once. See -[Hygiene](docs/implementation/README.md#hygiene-exports-markers-and-reserved-identifiers). +[Hygiene](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/README.md#hygiene-exports-markers-and-reserved-identifiers). #### Spans: for the compiler *and* the IDE @@ -295,7 +311,7 @@ invocation), which misleads both tools, so confirm both for each macro: Only the caret half is pinned by a test (a `trybuild` `.stderr` fixture records each caret's exact position, so a regression changes the snapshot). The IDE half has no fixture and must be checked by hand in an editor — and cross-crate, since the derived-name leak is invisible from inside the defining -crate. See [Spans](docs/implementation/README.md#spans-aim-generated-items-at-the-token-the-user-wrote) +crate. See [Spans](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/README.md#spans-aim-generated-items-at-the-token-the-user-wrote) for the full mechanism and the `delegate_components!`/`#[cgp_impl]` worked examples. ### Keep every view in sync and verify diff --git a/README.md b/README.md index 918799c3..2360fea7 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,8 @@ Learn CGP at the project website, **[contextgeneric.dev](https://contextgeneric. > [!NOTE] > The crates' API docs on `docs.rs` are still sparse. The website is the place to start while the inline documentation is filled in. +Contributors and coding agents have a second, deeper source: the [CGP knowledge base](https://github.com/contextgeneric/cgp-knowledge-base), which holds the exhaustive per-construct reference, the concept overviews, the writing guides, the compile-error catalog, the worked examples, and the internals of the macros in this repository. It is written for AI agents rather than for learners, so start at the website if you are learning CGP and reach for the knowledge base when you need the exact semantics of a construct or are changing this repository. Start from [AGENTS.md](AGENTS.md) for the conventions this repository follows, and [sibling-projects.md](sibling-projects.md) for the related repositories. + ## Project status CGP works today on stable Rust and already offers a broad feature set: components and providers, abstract types, extensible records and variants, a handler and computation family, namespaces, and modular error handling. It is also young — the paradigm and its ecosystem are still evolving, and v0.8.0 is a pre-release — so adopting it is a real decision rather than a foregone one. diff --git a/crates/macros/cgp-macro-core/AGENTS.md b/crates/macros/cgp-macro-core/AGENTS.md index da8d2062..7b62abee 100644 --- a/crates/macros/cgp-macro-core/AGENTS.md +++ b/crates/macros/cgp-macro-core/AGENTS.md @@ -80,7 +80,7 @@ into the existing stage rather than collapsing the pipeline. [src/macros/keyword.rs](src/macros/keyword.rs) and `types/keyword*.rs`). **6. Keep inline docs brief and current as you go.** When you review a file — whether to change it -or to write its [implementation document](../../../docs/implementation/README.md) — improve the +or to write its [implementation document](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/implementation/README.md) — improve the inline docs in the same pass. Add a one-line `///` to any public struct, trait, or function that lacks one, saying what it is or does (for a pipeline stage, its role in the sequence); prefer naming the *why* or a corner case over restating the signature. Fix a doc that no longer matches the code, diff --git a/crates/macros/cgp-macro-core/src/types/attributes/prefix.rs b/crates/macros/cgp-macro-core/src/types/attributes/prefix.rs index ee18e3e1..d699b897 100644 --- a/crates/macros/cgp-macro-core/src/types/attributes/prefix.rs +++ b/crates/macros/cgp-macro-core/src/types/attributes/prefix.rs @@ -32,7 +32,7 @@ impl PrefixAttribute { // Insert the components table as the leading generic. Position 0 sits // ahead of any lifetime, which the `parse_internal! { impl #type_generics // … }` re-parse below normalizes (`syn` re-emits lifetimes first). See - // docs/implementation/README.md, "Generic-parameter insertion and + // cgp-knowledge-base/cgp/implementation/README.md, "Generic-parameter insertion and // lifetime ordering". type_generics .params diff --git a/crates/macros/cgp-macro-core/src/types/attributes/use_provider/attribute.rs b/crates/macros/cgp-macro-core/src/types/attributes/use_provider/attribute.rs index f3f55b0e..c7229d44 100644 --- a/crates/macros/cgp-macro-core/src/types/attributes/use_provider/attribute.rs +++ b/crates/macros/cgp-macro-core/src/types/attributes/use_provider/attribute.rs @@ -25,7 +25,7 @@ impl UseProviderAttribute { // Insert the context as the inner provider bound's leading type // argument. Position 0 sits ahead of any lifetime argument, which the // `parse_internal(#bound)` re-parse below normalizes (`syn` re-emits - // lifetimes first). See docs/implementation/README.md, + // lifetimes first). See cgp-knowledge-base/cgp/implementation/README.md, // "Generic-parameter insertion and lifetime ordering". bound .type_args diff --git a/crates/macros/cgp-macro-core/src/types/cgp_auto_getter/blanket.rs b/crates/macros/cgp-macro-core/src/types/cgp_auto_getter/blanket.rs index e2e3b856..1f0078ef 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_auto_getter/blanket.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_auto_getter/blanket.rs @@ -23,7 +23,7 @@ pub fn derive_blanket_impl( // Insert the context as the leading impl generic. Position 0 is safe with a // lifetime present because `syn::Generics::to_tokens` emits lifetimes first. - // See docs/implementation/README.md, "Generic-parameter insertion and + // See cgp-knowledge-base/cgp/implementation/README.md, "Generic-parameter insertion and // lifetime ordering". generics .params diff --git a/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_consumer_impl.rs b/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_consumer_impl.rs index c5aace26..29c09c70 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_consumer_impl.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_consumer_impl.rs @@ -22,7 +22,7 @@ impl PreprocessedCgpComponent { // Insert the context as the leading type argument. Position 0 sits // ahead of any lifetime argument, which the `parse_internal` re-parse // on the next line normalizes (`syn` re-emits lifetimes first). See - // docs/implementation/README.md, "Generic-parameter insertion and + // cgp-knowledge-base/cgp/implementation/README.md, "Generic-parameter insertion and // lifetime ordering". provider_type_generics .generics @@ -37,7 +37,7 @@ impl PreprocessedCgpComponent { // Insert the context as the leading impl generic. Position 0 is safe // with a lifetime present because `syn::Generics::to_tokens` emits - // lifetimes first. See docs/implementation/README.md, + // lifetimes first. See cgp-knowledge-base/cgp/implementation/README.md, // "Generic-parameter insertion and lifetime ordering". generics .params diff --git a/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_provider_blanket_impl.rs b/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_provider_blanket_impl.rs index f953d90b..c60860ce 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_provider_blanket_impl.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_provider_blanket_impl.rs @@ -37,7 +37,7 @@ impl PreprocessedCgpComponent { // Insert the provider as the leading impl generic. Position 0 is safe // with a lifetime present because `syn::Generics::to_tokens` emits - // lifetimes first. See docs/implementation/README.md, + // lifetimes first. See cgp-knowledge-base/cgp/implementation/README.md, // "Generic-parameter insertion and lifetime ordering". impl_generics .params diff --git a/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_provider_trait.rs b/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_provider_trait.rs index 35bea210..5bc236e6 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_provider_trait.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/to_provider_trait.rs @@ -28,7 +28,7 @@ impl PreprocessedCgpComponent { // Insert the context as the provider trait's leading generic parameter. // Position 0 is safe even when the trait already has a lifetime, because // `syn::Generics::to_tokens` emits lifetimes ahead of type/const - // parameters. See docs/implementation/README.md, + // parameters. See cgp-knowledge-base/cgp/implementation/README.md, // "Generic-parameter insertion and lifetime ordering". provider_trait .generics diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/finalize_build_impl.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/finalize_build_impl.rs index da20bb30..5ac35e2b 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/finalize_build_impl.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/finalize_build_impl.rs @@ -51,6 +51,6 @@ pub fn derive_finalize_build_impl( }; // Key the error span on the struct name the user wrote, not the whole - // derive. See docs/implementation/README.md#spans. + // derive. See cgp-knowledge-base/cgp/implementation/README.md#spans. override_item_span(context_ident.span(), &item_impl) } diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/has_builder_impl.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/has_builder_impl.rs index 57c60b7c..24af85c7 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/has_builder_impl.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/has_builder_impl.rs @@ -46,6 +46,6 @@ pub fn derive_has_builder_impl( }; // Key the error span on the struct name the user wrote, not the whole - // derive. See docs/implementation/README.md#spans. + // derive. See cgp-knowledge-base/cgp/implementation/README.md#spans. override_item_span(context_ident.span(), &item_impl) } diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/has_field_impls.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/has_field_impls.rs index 2abda01c..5b91fbb2 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/has_field_impls.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/has_field_impls.rs @@ -58,7 +58,8 @@ pub fn derive_has_field_impls( }; // Point an error on this per-field impl at the field the user wrote - // rather than at the whole derive. See docs/implementation/README.md#spans. + // rather than at the whole derive. See + // cgp-knowledge-base/cgp/implementation/README.md#spans. let field_span = current_field .ident .as_ref() diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/into_builder_impl.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/into_builder_impl.rs index 08a3064b..7b2b6c58 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/into_builder_impl.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/into_builder_impl.rs @@ -49,6 +49,6 @@ pub fn derive_into_builder_impl( }; // Key the error span on the struct name the user wrote, not the whole - // derive. See docs/implementation/README.md#spans. + // derive. See cgp-knowledge-base/cgp/implementation/README.md#spans. override_item_span(context_ident.span(), &item_impl) } diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/partial_data.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/partial_data.rs index 253c4a44..ae6557d1 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/partial_data.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/partial_data.rs @@ -34,6 +34,6 @@ pub fn derive_partial_data_impl_from_struct( }; // Key the error span on the struct name the user wrote, not the whole - // derive. See docs/implementation/README.md#spans. + // derive. See cgp-knowledge-base/cgp/implementation/README.md#spans. override_item_span(context_ident.span(), &item_impl) } diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/update_field_impls.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/update_field_impls.rs index d785e06b..f30cdd16 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/update_field_impls.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/update_field_impls.rs @@ -102,7 +102,8 @@ pub fn derive_update_field_impls( }; // Point an error on this per-field impl at the field the user wrote - // rather than at the whole derive. See docs/implementation/README.md#spans. + // rather than at the whole derive. See + // cgp-knowledge-base/cgp/implementation/README.md#spans. let field_span = current_field .ident .as_ref() diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/extract_field_impls.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/extract_field_impls.rs index 9f7a549e..2cb348f1 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/extract_field_impls.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/extract_field_impls.rs @@ -118,7 +118,8 @@ pub fn derive_extract_field_impls( }; // Point an error on this per-variant impl at the variant the user wrote - // rather than at the whole derive. See docs/implementation/README.md#spans. + // rather than at the whole derive. See + // cgp-knowledge-base/cgp/implementation/README.md#spans. item_impls.push(override_item_span( current_variant.ident.span(), &item_impl, diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/finalize_extract_impl.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/finalize_extract_impl.rs index 0847b028..f5080142 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/finalize_extract_impl.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/finalize_extract_impl.rs @@ -63,6 +63,6 @@ pub fn derive_finalize_extract_impl( }; // Key the error span on the enum name the user wrote, not the whole derive. - // See docs/implementation/README.md#spans. + // See cgp-knowledge-base/cgp/implementation/README.md#spans. override_item_span(context_enum.ident.span(), &item_impl) } diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/has_extractor_impl.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/has_extractor_impl.rs index f5be8111..e20a9631 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/has_extractor_impl.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/has_extractor_impl.rs @@ -64,7 +64,7 @@ pub fn derive_has_extractor_impl( }; // Key the error span on the enum name the user wrote, not the whole derive. - // See docs/implementation/README.md#spans. + // See cgp-knowledge-base/cgp/implementation/README.md#spans. override_item_span(context_ident.span(), &item_impl) } @@ -141,7 +141,7 @@ pub fn derive_has_extractor_ref_impl( }; // Key the error span on the enum name the user wrote, not the whole derive. - // See docs/implementation/README.md#spans. + // See cgp-knowledge-base/cgp/implementation/README.md#spans. override_item_span(context_ident.span(), &item_impl) } @@ -215,6 +215,6 @@ pub fn derive_has_extractor_mut_impl( }; // Key the error span on the enum name the user wrote, not the whole derive. - // See docs/implementation/README.md#spans. + // See cgp-knowledge-base/cgp/implementation/README.md#spans. override_item_span(context_ident.span(), &item_impl) } diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/partial_data.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/partial_data.rs index 7cc33d9b..5518f6cb 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/partial_data.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/partial_data.rs @@ -58,6 +58,6 @@ pub fn derive_partial_data_impl_from_enum( }; // Key the error span on the enum name the user wrote, not the whole derive. - // See docs/implementation/README.md#spans. + // See cgp-knowledge-base/cgp/implementation/README.md#spans. override_item_span(context_ident.span(), &item_impl) } diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs index 751e8654..b6a52584 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs @@ -36,7 +36,7 @@ pub fn derive_from_variant_from_enum(item_enum: &ItemEnum) -> syn::Result syn::Resu // same tag, say — at the field the user wrote rather than at the // whole `#[derive(HasField)]`, which is where the impl's // `call_site`-spanned `impl`/`{ … }` boundary would otherwise put - // the caret. See docs/implementation/README.md#spans. + // the caret. See cgp-knowledge-base/cgp/implementation/README.md#spans. let field_span = field_ident.span(); let field_symbol = Symbol::from_ident(field_ident.clone()); diff --git a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/derive_enum.rs b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/derive_enum.rs index 43fd86ab..e44845e3 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/derive_enum.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/derive_enum.rs @@ -48,7 +48,7 @@ pub fn derive_has_fields_impls_from_enum(item_enum: &ItemEnum) -> syn::Result syn::Res // These impls are all keyed on the whole struct, so aim an error on any of // their headers at the struct name the user wrote rather than at the whole - // `#[derive(...)]`. See docs/implementation/README.md#spans. + // `#[derive(...)]`. See cgp-knowledge-base/cgp/implementation/README.md#spans. [ has_fields_impl, has_fields_ref_impl, diff --git a/crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs b/crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs index 1045c735..fab6ff05 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs @@ -94,7 +94,7 @@ impl PreprocessedItemCgpFn { item_impl.generics = generics.clone(); // Insert the context as the leading impl generic. Position 0 is safe with // a lifetime present because `syn::Generics::to_tokens` emits lifetimes - // first. See docs/implementation/README.md, "Generic-parameter insertion + // first. See cgp-knowledge-base/cgp/implementation/README.md, "Generic-parameter insertion // and lifetime ordering". item_impl .generics diff --git a/crates/macros/cgp-macro-core/src/types/cgp_impl/item.rs b/crates/macros/cgp-macro-core/src/types/cgp_impl/item.rs index 40953696..556961c5 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_impl/item.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_impl/item.rs @@ -54,7 +54,7 @@ impl ItemCgpImpl { // Insert the context as the leading impl generic. Position 0 is // safe even when the impl already has a lifetime, because // `syn::Generics::to_tokens` emits lifetimes ahead of type/const - // parameters. See docs/implementation/README.md, + // parameters. See cgp-knowledge-base/cgp/implementation/README.md, // "Generic-parameter insertion and lifetime ordering". item_impl .generics diff --git a/crates/macros/cgp-macro-core/src/types/cgp_impl/lowered.rs b/crates/macros/cgp-macro-core/src/types/cgp_impl/lowered.rs index eebf9c0a..85eeb09e 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_impl/lowered.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_impl/lowered.rs @@ -92,7 +92,7 @@ impl LoweredCgpImpl { // Position 0 places it ahead of any lifetime argument, which is invalid // Rust ordering on its own — it is only safe because the path is // re-parsed through `syn` below (`parse_internal(… .to_token_stream())`), - // and `syn` re-emits lifetimes first. See docs/implementation/README.md, + // and `syn` re-emits lifetimes first. See cgp-knowledge-base/cgp/implementation/README.md, // "Generic-parameter insertion and lifetime ordering". provider_trait_path .type_args diff --git a/crates/macros/cgp-macro-core/src/types/cgp_type/item.rs b/crates/macros/cgp-macro-core/src/types/cgp_type/item.rs index 4d2e4687..c5837ac1 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_type/item.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_type/item.rs @@ -42,7 +42,7 @@ impl ItemCgpType { let mut generics = provider_trait.generics.clone(); // Insert the abstract type as the leading generic. Position 0 is safe // with a lifetime present because `syn::Generics::to_tokens` emits - // lifetimes first. See docs/implementation/README.md, "Generic-parameter + // lifetimes first. See cgp-knowledge-base/cgp/implementation/README.md, "Generic-parameter // insertion and lifetime ordering". generics.params.insert(0, parse_internal!(#type_name)); diff --git a/crates/tests/AGENTS.md b/crates/tests/AGENTS.md index d27a62b4..ba70f9db 100644 --- a/crates/tests/AGENTS.md +++ b/crates/tests/AGENTS.md @@ -5,6 +5,11 @@ moving, or refactoring any test here. Invoke the `/cgp` skill first — every te in this tree is CGP code, and the skill is the authoritative source for CGP semantics and vocabulary. +This repository's documentation lives in the sibling +[`cgp-knowledge-base`](https://github.com/contextgeneric/cgp-knowledge-base) repository, under its +`cgp/` directory, so a doc pointer below names a path there (`cgp-knowledge-base/cgp/…`) rather than a +local one. See [../../sibling-projects.md](../../sibling-projects.md) for finding that checkout. + The test suite has two jobs, split across crates: - **`cgp-tests`** is the main suite: realistic example code that must **compile and @@ -36,7 +41,7 @@ so on. The right granularity is driven by the feature, its implementation complexity, and how many cases are needed to cover it exhaustively — **not** by mirroring the -concept documents under `docs/concepts/`. The names may coincide, but the split is +concept documents under `cgp-knowledge-base/cgp/concepts/`. The names may coincide, but the split is chosen for coverage. **When a category accumulates too many test cases to stay coherent, split it into finer categories** rather than letting it sprawl; prefer splitting early. @@ -71,10 +76,10 @@ that test exercises. Open every test file with a brief comment stating **what behavior it exercises**, and annotate individual tricky cases inline. Link to the owning **implementation -document** — the one under `docs/implementation/` whose Tests and Snapshots -sections index this test (for example `// see docs/implementation/entrypoints/cgp_impl.md`); +document** — the one under `cgp-knowledge-base/cgp/implementation/` whose Tests and Snapshots +sections index this test (for example `// see cgp-knowledge-base/cgp/implementation/entrypoints/cgp_impl.md`); that document is where test pointers live, since a reference document never links -to a test (per `docs/AGENTS.md`). You may additionally link to a reference +to a test (per the knowledge base's `cgp/AGENTS.md`). You may additionally link to a reference document when a reader needs the user-facing semantics. Tests link **to** the documentation; the reference documents never link back to a test. @@ -150,9 +155,9 @@ compiles even though the code would not), with a comment explaining **why** the is wrong and **what the correct output should be**. **Where a post-codegen class is documented.** Such a class is cataloged in the -[error catalog](../../docs/errors/README.md) under `docs/errors/`, the canonical +[error catalog](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/errors/README.md) under `cgp-knowledge-base/cgp/errors/`, the canonical reader-facing documentation for these errors (see -[docs/errors/AGENTS.md](../../docs/errors/AGENTS.md)): the class doc describes the raw +[cgp-knowledge-base/cgp/errors/AGENTS.md](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/errors/AGENTS.md)): the class doc describes the raw diagnostic *and* how cargo-cgp presents it, and links the backing `cargo-cgp` UI fixture as a GitHub URL. When a construct change alters such a diagnostic, the cross-project [sync rule](../../AGENTS.md) applies — update the `cargo-cgp` fixture and the class doc @@ -165,10 +170,9 @@ and its `cargo-cgp` fixture rather than to a local fixture. ## Keep the docs in sync This suite is one of the views of CGP's truth, alongside the macro implementation -in `cgp-macro-core`, the reference documents in `docs/reference`, the -implementation documents in `docs/implementation`, and the `/cgp` skill (see -`docs/AGENTS.md`). The implementation documents are the ones tightly coupled to -this suite: each macro's implementation document has a `## Tests` section linking +in `cgp-macro-core`, the knowledge base's `cgp/reference` and `cgp/implementation` +documents, and the `/cgp` skill (see the knowledge base's `cgp/AGENTS.md`). The +implementation documents are the ones tightly coupled to this suite: each macro's implementation document has a `## Tests` section linking every behavioral test and failure case that exercises it, and every entrypoint document a `## Snapshots` section indexing the expansion snapshots and calling out which variants are still missing. When a test reveals or pins a behavior worth diff --git a/crates/tests/README.md b/crates/tests/README.md index 176162a1..7bf38235 100644 --- a/crates/tests/README.md +++ b/crates/tests/README.md @@ -29,7 +29,7 @@ migrated to `cargo-cgp`'s UI test suite, which snapshots the readable, root-caus errors `cargo-cgp` renders for each class (its `.rust.stderr` still records the raw compiler output as the "before"). `cargo-cgp` is CGP's first-class error toolchain, so those diagnostics are pinned where the tool that improves them lives; the -[error catalog](../../docs/errors/README.md) links each class to the fixture that +[error catalog](https://github.com/contextgeneric/cgp-knowledge-base/blob/main/cgp/errors/README.md) links each class to the fixture that backs it. See [cargo-cgp's UI tests](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md). diff --git a/crates/tests/cgp-macro-tests/tests/invalid_expansion_tests.rs b/crates/tests/cgp-macro-tests/tests/invalid_expansion_tests.rs index 4384b986..1f9570d8 100644 --- a/crates/tests/cgp-macro-tests/tests/invalid_expansion_tests.rs +++ b/crates/tests/cgp-macro-tests/tests/invalid_expansion_tests.rs @@ -13,7 +13,7 @@ //! 2. add a code comment explaining **why** the output is wrong and **what the //! correct output should be**; //! 3. record the limitation in the owning reference document's `## Known issues` -//! section (per docs/AGENTS.md), and link from the test to that document. +//! section (per cgp-knowledge-base/cgp/AGENTS.md), and link from the test to that document. //! //! No cases are enumerated yet; see crates/tests/AGENTS.md ("Migration status"). #![allow(dead_code)] diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs index 98b1c51c..ffd0f669 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs @@ -1,8 +1,9 @@ //! `#[cgp_component]` rejects inputs it cannot lower into a component: a //! non-trait item, and a trait carrying a const generic parameter. //! -//! See docs/implementation/entrypoints/cgp_component.md (Tests) for these failure -//! cases, and docs/reference/macros/cgp_component.md for the user-facing semantics. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_component.md (Tests) for these failure +//! cases, and cgp-knowledge-base/cgp/reference/macros/cgp_component.md for the user-facing +//! semantics. use quote::quote; diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_fn.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_fn.rs index 49da6fd6..d59a78ae 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_fn.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_fn.rs @@ -9,8 +9,8 @@ //! `trybuild` compile-fail fixture, which is reserved for input the macro accepts //! but whose expansion then fails to compile. //! -//! See docs/implementation/entrypoints/cgp_fn.md (Tests) for these failure cases, -//! and docs/reference/attributes/implicit.md for the user-facing rules on where +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_fn.md (Tests) for these failure cases, +//! and cgp-knowledge-base/cgp/reference/attributes/implicit.md for the user-facing rules on where //! `#[implicit]` may appear. use quote::quote; diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_impl.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_impl.rs index 2f2f89fb..8bf9d70d 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_impl.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_impl.rs @@ -2,8 +2,8 @@ //! with no provider name, a `#[cgp_impl(Self)]` block missing its `for` clause, //! and a non-`impl` item. //! -//! See docs/implementation/entrypoints/cgp_impl.md (Tests) for these failure -//! cases, and docs/reference/macros/cgp_impl.md for the user-facing semantics. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_impl.md (Tests) for these failure +//! cases, and cgp-knowledge-base/cgp/reference/macros/cgp_impl.md for the user-facing semantics. use quote::quote; diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_namespace.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_namespace.rs index 120e232a..00c9e517 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_namespace.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_namespace.rs @@ -4,8 +4,9 @@ //! discarding it. It rejects an attribute on a `:` mapping key, on a `=>` redirect //! key, and on a key inside a `for <..> in ..` loop. //! -//! See docs/implementation/entrypoints/cgp_namespace.md (Tests) for these failure -//! cases, and docs/reference/macros/cgp_namespace.md for the user-facing semantics. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_namespace.md (Tests) for these failure +//! cases, and cgp-knowledge-base/cgp/reference/macros/cgp_namespace.md for the user-facing +//! semantics. use quote::quote; diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_provider.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_provider.rs index b5a5c43a..7527b1b1 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_provider.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_provider.rs @@ -6,8 +6,9 @@ //! //! `#[cgp_new_provider]` shares this stack and rejects the same inputs. //! -//! See docs/implementation/entrypoints/cgp_provider.md (Tests) for these failure -//! cases, and docs/reference/macros/cgp_provider.md for the user-facing semantics. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_provider.md (Tests) for these failure +//! cases, and cgp-knowledge-base/cgp/reference/macros/cgp_provider.md for the user-facing +//! semantics. use quote::quote; diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/check_components.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/check_components.rs index 9bf425e0..ff492a77 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/check_components.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/check_components.rs @@ -3,8 +3,8 @@ //! that verifies nothing), a repeated `#[check_providers]` or `#[check_trait]`, //! and any unrecognized attribute. //! -//! See docs/implementation/entrypoints/check_components.md (Tests) for these -//! failure cases, and docs/reference/macros/check_components.md for the +//! See cgp-knowledge-base/cgp/implementation/entrypoints/check_components.md (Tests) for these +//! failure cases, and cgp-knowledge-base/cgp/reference/macros/check_components.md for the //! user-facing semantics. use quote::quote; diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs index 1c8e0fe4..d97080fa 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs @@ -4,8 +4,8 @@ //! rather than silently drop. It also rejects a braceless `open` header that //! lists more than one component, since the braceless form opens exactly one. //! -//! See docs/implementation/entrypoints/delegate_components.md (Tests) for these -//! failure cases, and docs/reference/macros/delegate_components.md for the +//! See cgp-knowledge-base/cgp/implementation/entrypoints/delegate_components.md (Tests) for these +//! failure cases, and cgp-knowledge-base/cgp/reference/macros/delegate_components.md for the //! user-facing semantics. use quote::quote; diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/derive_cgp_data.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/derive_cgp_data.rs index 25227195..385d431c 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/derive_cgp_data.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/derive_cgp_data.rs @@ -6,7 +6,7 @@ //! confirms the umbrella propagates it; the non-struct-or-enum rejection is //! unique to `ItemCgpData`'s parser. //! -//! See docs/implementation/entrypoints/derive_cgp_data.md (Known issues, Tests). +//! See cgp-knowledge-base/cgp/implementation/entrypoints/derive_cgp_data.md (Known issues, Tests). use quote::quote; diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/derive_from_variant.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/derive_from_variant.rs index ebab08e1..06fcb3cc 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/derive_from_variant.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/derive_from_variant.rs @@ -2,8 +2,8 @@ //! their codegen cannot lower: a variant that does not carry exactly one unnamed //! field, and (for `CgpVariant`) a non-enum item. //! -//! See docs/implementation/entrypoints/derive_from_variant.md (Known issues, -//! Tests) and docs/implementation/entrypoints/derive_cgp_variant.md for the +//! See cgp-knowledge-base/cgp/implementation/entrypoints/derive_from_variant.md (Known issues, +//! Tests) and cgp-knowledge-base/cgp/implementation/entrypoints/derive_cgp_variant.md for the //! user-facing semantics. use quote::quote; diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/getters.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/getters.rs index 55e53b56..308e8a03 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/getters.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/getters.rs @@ -8,8 +8,9 @@ //! allowed, only alongside exactly one method. `#[cgp_auto_getter]` additionally //! rejects any attribute argument, since it has no provider name or keys to accept. //! -//! See docs/implementation/asts/cgp_getter.md (Tests) for these failure cases and -//! docs/reference/macros/cgp_auto_getter.md for the user-facing getter-method rules. +//! See cgp-knowledge-base/cgp/implementation/asts/cgp_getter.md (Tests) for these failure cases and +//! cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md for the user-facing getter-method +//! rules. use quote::quote; diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/use_type.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/use_type.rs index 04bf5eab..68e48a84 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/use_type.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/use_type.rs @@ -3,8 +3,9 @@ //! same identifier or alias — whether across specs or within one braced list, and //! on any host macro. //! -//! See docs/implementation/asts/attributes.md (Tests) for these failure cases and -//! docs/reference/attributes/use_type.md for the user-facing semantics. +//! See cgp-knowledge-base/cgp/implementation/asts/attributes/README.md (Tests) for these +//! failure cases and cgp-knowledge-base/cgp/reference/attributes/use_type.md for the +//! user-facing semantics. use quote::quote; diff --git a/crates/tests/cgp-tests/tests/abstract_types/cgp_type_bounded.rs b/crates/tests/cgp-tests/tests/abstract_types/cgp_type_bounded.rs index bfd04d0d..d844f55a 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/cgp_type_bounded.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/cgp_type_bounded.rs @@ -7,7 +7,8 @@ //! `HasScalarType` is written with a plain `#[cgp_type]` (its simple expansion is //! pinned in `cgp_type_macro`). //! -//! See docs/reference/macros/cgp_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_type; diff --git a/crates/tests/cgp-tests/tests/abstract_types/cgp_type_macro.rs b/crates/tests/cgp-tests/tests/abstract_types/cgp_type_macro.rs index f39255a7..c47c54ec 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/cgp_type_macro.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/cgp_type_macro.rs @@ -9,7 +9,8 @@ //! This is the reference snapshot for that expansion; other files reuse //! `#[cgp_type]` plainly. //! -//! See docs/reference/macros/cgp_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use cgp_macro_test_util::snapshot_cgp_type; diff --git a/crates/tests/cgp-tests/tests/abstract_types/cgp_type_self_referential.rs b/crates/tests/cgp-tests/tests/abstract_types/cgp_type_self_referential.rs index 3f364db4..1882c3b4 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/cgp_type_self_referential.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/cgp_type_self_referential.rs @@ -8,7 +8,8 @@ //! parameter). The `delegate_components!` wiring and its check are incidental //! scaffolding and use the plain macros. //! -//! See docs/reference/macros/cgp_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use core::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/abstract_types/cgp_type_unsized.rs b/crates/tests/cgp-tests/tests/abstract_types/cgp_type_unsized.rs index bf4af064..b5210ebd 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/cgp_type_unsized.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/cgp_type_unsized.rs @@ -8,7 +8,8 @@ //! extend the path with `PathCons`). A trailing `#[cgp_getter]` that //! depends on the abstract type is incidental scaffolding and is written plainly. //! -//! See docs/reference/macros/cgp_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_type; diff --git a/crates/tests/cgp-tests/tests/abstract_types/extend_component.rs b/crates/tests/cgp-tests/tests/abstract_types/extend_component.rs index 04186e9a..9a7a69e8 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/extend_component.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/extend_component.rs @@ -8,7 +8,8 @@ //! more verbose counterpart to `use_type_component`; `#[use_type]` is the //! preferred form. Wiring/check and `#[cgp_type]` are incidental scaffolding here. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use std::convert::Infallible; use std::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_auto_getter.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_auto_getter.rs index f6e26c66..20ce16a3 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_auto_getter.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_auto_getter.rs @@ -10,7 +10,8 @@ //! field. This pins that `#[use_type]` is supported on `#[cgp_auto_getter]`, not //! only on the three implementation macros. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs index 7be07062..4e6f163a 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs @@ -10,8 +10,9 @@ //! `#[cgp_type]` scaffolding and the delegation/check are incidental here and use //! the plain macros; the abstract-type expansion and wiring are owned elsewhere. //! -//! See docs/reference/attributes/use_type.md, docs/reference/providers/use_type.md, -//! and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md, +//! cgp-knowledge-base/cgp/reference/providers/use_type.md, and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use std::convert::Infallible; use std::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs index 2c9c7bc1..619378a1 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs @@ -8,7 +8,8 @@ //! kept because the abstract-type rewrite is the point; `#[cgp_type]` scaffolding //! is written plainly. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use std::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_deep_foreign.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_deep_foreign.rs index 38069d99..5efb13f7 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_deep_foreign.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_deep_foreign.rs @@ -13,7 +13,8 @@ //! generated trait. The `#[cgp_fn]` snapshot is kept because the multi-hop rewrite //! is the point. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs index f08f6950..5edd0dc0 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs @@ -7,7 +7,8 @@ //! may treat the value as an `f64` while the signature still speaks the abstract //! alias. The `#[cgp_fn]` snapshot is kept for the rewrite; `#[cgp_type]` is plain. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs index 84ece3a1..cefa72ef 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs @@ -10,7 +10,8 @@ //! dependencies `return_foo_or_bar` imports with `#[uses]`. All three `#[cgp_fn]` //! snapshots are kept because the abstract-type rewrite is the point. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use std::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_extend.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_extend.rs index 3b1324dc..424907c4 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_extend.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_extend.rs @@ -8,8 +8,9 @@ //! the `#[cgp_fn]` expansion mechanics themselves are owned by `implicit_arguments`. //! Two runtime contexts (`f32`/`f64`) confirm the generic result. //! -//! See docs/reference/attributes/use_type.md, docs/reference/macros/cgp_fn.md, -//! and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md, +//! cgp-knowledge-base/cgp/reference/macros/cgp_fn.md, and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use core::f64; use std::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs index 67718ed6..6521b0ca 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs @@ -11,7 +11,8 @@ //! generated trait for the concrete `Types`. The `#[cgp_fn]` snapshot is kept for //! the rewrite; `#[cgp_type]` is plain. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use std::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs index 687fd465..6904533e 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs @@ -9,7 +9,8 @@ //! gains `::Types: HasScalarType`. The `#[cgp_fn]` //! snapshot is kept for the rewrite; both `#[cgp_type]` traits are written plainly. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs index 9a48d45b..fb8bad05 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs @@ -11,7 +11,8 @@ //! All three `#[cgp_fn]` snapshots are kept because the abstract-type rewrite is //! the point. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs index 04b8c634..fbdaecf6 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs @@ -13,7 +13,8 @@ //! plain. `CheckRectangle` asserts the concrete `Rectangle` implements the //! generated trait. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use std::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_reverse_order.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_reverse_order.rs index e992b66c..0b72b656 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_reverse_order.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_reverse_order.rs @@ -14,7 +14,8 @@ //! `deep` takes and returns a value of the deep type, so the test asserts a //! concrete value flows through the fully-grounded signature at runtime. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs index 4d86b6c6..8e43a3bb 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs @@ -15,7 +15,8 @@ //! nothing else names the bound. This is the regression guard for the foreign //! bound being dropped from the trait. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use std::convert::Infallible; use std::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign_getter.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign_getter.rs index 2a7557d6..393a0295 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign_getter.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign_getter.rs @@ -13,8 +13,8 @@ //! type is that qualified associated type, so the context supplies the field while //! `App` supplies the concrete `UserId`. //! -//! See docs/guides/importing-abstract-types.md and -//! docs/reference/attributes/use_type.md. +//! See cgp-knowledge-base/cgp/guides/importing-abstract-types.md and +//! cgp-knowledge-base/cgp/reference/attributes/use_type.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs index 1aab9574..cfb2f267 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs @@ -9,7 +9,8 @@ //! generic parameter — i.e. `Self::Error` resolution stays distinct from the //! `Error` type argument. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_path_qualified.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_path_qualified.rs index 415ae7c3..772307c7 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_path_qualified.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_path_qualified.rs @@ -8,7 +8,8 @@ //! path-qualified trait becomes the supertrait/bound. `RectangleArea` provides the //! area, and `Rectangle` fixes the scalar with `UseType`. //! -//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_type.md and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. use std::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_uses_supertrait.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_uses_supertrait.rs index 94b89803..d622b5e4 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_uses_supertrait.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_uses_supertrait.rs @@ -11,8 +11,8 @@ //! `bar` forwards to `create_foo`, so this asserts a value flows end to end through //! the transitively-required, then explicitly re-imported, abstract type. //! -//! See docs/guides/declaring-dependencies.md and -//! docs/guides/importing-abstract-types.md. +//! See cgp-knowledge-base/cgp/guides/declaring-dependencies.md and +//! cgp-knowledge-base/cgp/guides/importing-abstract-types.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/abstract_types_tests.rs b/crates/tests/cgp-tests/tests/abstract_types_tests.rs index f0707e8e..2af27e46 100644 --- a/crates/tests/cgp-tests/tests/abstract_types_tests.rs +++ b/crates/tests/cgp-tests/tests/abstract_types_tests.rs @@ -9,8 +9,10 @@ //! owns the canonical `#[cgp_type]` macro-expansion snapshots and the //! abstract-type-rewriting snapshots for `#[use_type]`. //! -//! See docs/reference/macros/cgp_type.md, docs/reference/attributes/use_type.md, -//! docs/reference/providers/use_type.md, and docs/concepts/abstract-types.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_type.md, +//! cgp-knowledge-base/cgp/reference/attributes/use_type.md, +//! cgp-knowledge-base/cgp/reference/providers/use_type.md, and +//! cgp-knowledge-base/cgp/concepts/abstract-types.md. #![allow(dead_code)] #![allow(clippy::disallowed_names)] diff --git a/crates/tests/cgp-tests/tests/async_and_send/cgp_fn_async.rs b/crates/tests/cgp-tests/tests/async_and_send/cgp_fn_async.rs index ee33678f..fb7ffb7a 100644 --- a/crates/tests/cgp-tests/tests/async_and_send/cgp_fn_async.rs +++ b/crates/tests/cgp-tests/tests/async_and_send/cgp_fn_async.rs @@ -6,7 +6,7 @@ //! still pulled from the context's fields. This is the async variant of the //! `#[cgp_fn]` expansion, which the `async_and_send` concept owns. //! -//! See docs/reference/macros/async_trait.md. +//! See cgp-knowledge-base/cgp/reference/macros/async_trait.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/async_and_send/spawn.rs b/crates/tests/cgp-tests/tests/async_and_send/spawn.rs index d14a9f13..d7b8757e 100644 --- a/crates/tests/cgp-tests/tests/async_and_send/spawn.rs +++ b/crates/tests/cgp-tests/tests/async_and_send/spawn.rs @@ -10,7 +10,8 @@ //! is concrete, the compiler can prove `Send` for the produced future. This is //! the workaround CGP uses until Rust's Return Type Notation (RTN) is stabilized. //! -//! See docs/reference/components/runner.md and docs/concepts/send-bounds.md. +//! See cgp-knowledge-base/cgp/reference/components/runner.md and +//! cgp-knowledge-base/cgp/concepts/send-bounds.md. use core::convert::Infallible; use core::future::Future; diff --git a/crates/tests/cgp-tests/tests/async_and_send_tests.rs b/crates/tests/cgp-tests/tests/async_and_send_tests.rs index e4d9d15a..ff1d54cc 100644 --- a/crates/tests/cgp-tests/tests/async_and_send_tests.rs +++ b/crates/tests/cgp-tests/tests/async_and_send_tests.rs @@ -7,8 +7,9 @@ //! context recovers the `Send` bound through a proxy `SendRunner` impl without //! annotating `Send` throughout the abstract code. //! -//! See docs/reference/macros/async_trait.md, docs/reference/components/runner.md, -//! and docs/concepts/send-bounds.md. +//! See cgp-knowledge-base/cgp/reference/macros/async_trait.md, +//! cgp-knowledge-base/cgp/reference/components/runner.md, and +//! cgp-knowledge-base/cgp/concepts/send-bounds.md. #![allow(dead_code)] #![allow(clippy::disallowed_names)] diff --git a/crates/tests/cgp-tests/tests/basic_delegation/component_macro.rs b/crates/tests/cgp-tests/tests/basic_delegation/component_macro.rs index 3e1a9612..9d3f81a0 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/component_macro.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/component_macro.rs @@ -7,9 +7,9 @@ //! This is the reference snapshot for that expansion; other concepts reuse //! `#[cgp_component]` without re-snapshotting it. //! -//! See docs/implementation/entrypoints/cgp_component.md (Snapshots) for the index -//! of `#[cgp_component]` expansion snapshots, and docs/reference/macros/cgp_component.md -//! for the user-facing expansion. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_component.md (Snapshots) for the index +//! of `#[cgp_component]` expansion snapshots, and +//! cgp-knowledge-base/cgp/reference/macros/cgp_component.md for the user-facing expansion. use cgp_macro_test_util::snapshot_cgp_component; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_generic.rs b/crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_generic.rs index 9eddf898..5e28ea80 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_generic.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_generic.rs @@ -5,7 +5,7 @@ //! field while implementing `HasValue` directly. Each type argument resolves //! independently. //! -//! See docs/reference/macros/cgp_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_getter.rs b/crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_getter.rs index 6a84f235..b953507c 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_getter.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_getter.rs @@ -5,7 +5,7 @@ //! implementing `HasCount` by hand — CGP consumer traits are ordinary traits, so //! both routes coexist on the same context. //! -//! See docs/concepts/consumer-and-provider-traits.md. +//! See cgp-knowledge-base/cgp/concepts/consumer-and-provider-traits.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/default_methods.rs b/crates/tests/cgp-tests/tests/basic_delegation/default_methods.rs index cf1c0def..ab78005e 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/default_methods.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/default_methods.rs @@ -5,9 +5,10 @@ //! the distinct component-expansion variant with a supertrait bound and a default //! method, kept alongside the plain snapshot in `component_macro`. //! -//! See docs/implementation/entrypoints/cgp_component.md (Snapshots) for this -//! supertrait-plus-default-method variant, and docs/reference/macros/cgp_component.md -//! and docs/reference/attributes/extend.md for the user-facing semantics. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_component.md (Snapshots) for this +//! supertrait-plus-default-method variant, and +//! cgp-knowledge-base/cgp/reference/macros/cgp_component.md and +//! cgp-knowledge-base/cgp/reference/attributes/extend.md for the user-facing semantics. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_component; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/delegate_array_key.rs b/crates/tests/cgp-tests/tests/basic_delegation/delegate_array_key.rs index e25ede51..895820b7 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/delegate_array_key.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/delegate_array_key.rs @@ -4,7 +4,7 @@ //! An array key `[A, B]: Provider` expands to one `DelegateComponent` + //! `IsProviderFor` impl pair per key, all pointing at the same provider. //! -//! See docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use cgp::prelude::DelegateComponent; use cgp_macro_test_util::snapshot_delegate_components; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/delegate_components_macro.rs b/crates/tests/cgp-tests/tests/basic_delegation/delegate_components_macro.rs index 1c8ad9c8..243e6e70 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/delegate_components_macro.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/delegate_components_macro.rs @@ -6,7 +6,7 @@ //! owns the reference snapshot, including the `->` forwarding form that delegates //! to another table's entry. //! -//! See docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_delegate_components; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/delegate_generic_nested_value.rs b/crates/tests/cgp-tests/tests/basic_delegation/delegate_generic_nested_value.rs index 603f2fe1..d569107b 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/delegate_generic_nested_value.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/delegate_generic_nested_value.rs @@ -4,7 +4,7 @@ //! ` BarKey: UseDelegate { … }>` threads the entry generic //! `T` through both the outer key and the inner generated table struct. //! -//! See docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/delegate_generic_table.rs b/crates/tests/cgp-tests/tests/basic_delegation/delegate_generic_table.rs index fde79971..29dd1d19 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/delegate_generic_table.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/delegate_generic_table.rs @@ -5,7 +5,7 @@ //! generated impl, and an entry key may introduce its own extra generics //! (` BazKey`). //! -//! See docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_components.md. // The header lifts `'a` onto every generated impl, but only the `BarKey<'a, T1>` // entry uses it, so the `FooKey`/`BazKey` impls carry an intentionally-unused diff --git a/crates/tests/cgp-tests/tests/basic_delegation/delegate_nested_use_delegate.rs b/crates/tests/cgp-tests/tests/basic_delegation/delegate_nested_use_delegate.rs index 78d68230..0b419fe0 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/delegate_nested_use_delegate.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/delegate_nested_use_delegate.rs @@ -3,8 +3,8 @@ //! This builds a two-level dispatch table inline: `BarKey` delegates to //! `UseDelegate`, and `BarValue` is its own table keyed by `BazKey`. //! -//! See docs/reference/macros/delegate_components.md and -//! docs/reference/providers/use_delegate.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_components.md and +//! cgp-knowledge-base/cgp/reference/providers/use_delegate.md. use cgp::core::component::UseDelegate; use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_array_key.rs b/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_array_key.rs index fe492836..4c410019 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_array_key.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_array_key.rs @@ -4,7 +4,7 @@ //! This is a compile-time check that the array-key plus nested-`new` forms parse //! and expand together. //! -//! See docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use cgp::core::component::UseDelegate; use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_generic_struct.rs b/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_generic_struct.rs index 22e7fd45..75453a93 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_generic_struct.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_generic_struct.rs @@ -3,7 +3,7 @@ //! `delegate_components! { new MyComponents { … } }` defines //! `struct MyComponents` and wires a family of tables at once. //! -//! See docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_struct.rs b/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_struct.rs index 906d2c91..c4c8453b 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_struct.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/delegate_new_struct.rs @@ -5,7 +5,7 @@ //! and its `DelegateComponent` impls together. This is a compile-time check: the //! `CheckDelegates` bound proves the table resolves as written. //! -//! See docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/impl_self.rs b/crates/tests/cgp-tests/tests/basic_delegation/impl_self.rs index c5a8a956..137969fd 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/impl_self.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/impl_self.rs @@ -6,8 +6,8 @@ //! forwarding to a reusable provider. This is the direct-impl end of the //! modularity spectrum, where no wiring table is involved. //! -//! See docs/reference/macros/cgp_impl.md and -//! docs/reference/attributes/use_provider.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_impl.md and +//! cgp-knowledge-base/cgp/reference/attributes/use_provider.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/owned_receiver.rs b/crates/tests/cgp-tests/tests/basic_delegation/owned_receiver.rs index 840c4fa3..e853a3cb 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/owned_receiver.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/owned_receiver.rs @@ -6,8 +6,8 @@ //! owned-mutable receiver so the rewrite of every receiver shape — `&self`, //! `&mut self`, `self`, and `mut self` — stays covered. //! -//! See docs/implementation/entrypoints/cgp_impl.md and -//! docs/reference/macros/cgp_impl.md. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_impl.md and +//! cgp-knowledge-base/cgp/reference/macros/cgp_impl.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/provider_component_override.rs b/crates/tests/cgp-tests/tests/basic_delegation/provider_component_override.rs index f11e410e..29f3f293 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/provider_component_override.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/provider_component_override.rs @@ -8,7 +8,7 @@ //! `FooProviderComponent`, which does not exist here, so the generated //! `IsProviderFor` impl fails to resolve. //! -//! See docs/reference/macros/cgp_impl.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_impl.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_impl; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/provider_macro.rs b/crates/tests/cgp-tests/tests/basic_delegation/provider_macro.rs index b6930f5f..f480cdff 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/provider_macro.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/provider_macro.rs @@ -7,7 +7,7 @@ //! written with a plain `#[cgp_component]` (already snapshotted in //! `component_macro`), and a context wires the provider and calls it. //! -//! See docs/reference/macros/cgp_impl.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_impl.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_impl; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/self_in_macro.rs b/crates/tests/cgp-tests/tests/basic_delegation/self_in_macro.rs index 95a4b76c..80507e6a 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/self_in_macro.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/self_in_macro.rs @@ -9,8 +9,8 @@ //! left untouched. A value `self` is never followed by `::`, so the trailing //! `::` is what disambiguates the path form. //! -//! See docs/implementation/entrypoints/cgp_impl.md and -//! docs/reference/macros/cgp_impl.md. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_impl.md and +//! cgp-knowledge-base/cgp/reference/macros/cgp_impl.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/self_in_nested_item.rs b/crates/tests/cgp-tests/tests/basic_delegation/self_in_nested_item.rs index d7971e5c..15494b08 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/self_in_nested_item.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/self_in_nested_item.rs @@ -9,8 +9,8 @@ //! receiver would be turned into a context parameter and the `Display` impl would //! fail to compile. The outer `self.name()` is still rewritten to the context. //! -//! See docs/implementation/entrypoints/cgp_impl.md (Behavior and corner cases) -//! and docs/reference/macros/cgp_impl.md. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_impl.md (Behavior and corner cases) +//! and cgp-knowledge-base/cgp/reference/macros/cgp_impl.md. use core::fmt::{self, Display}; diff --git a/crates/tests/cgp-tests/tests/basic_delegation_tests.rs b/crates/tests/cgp-tests/tests/basic_delegation_tests.rs index a7abdf1b..b89b466d 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation_tests.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation_tests.rs @@ -5,8 +5,8 @@ //! with `#[cgp_impl]`, wiring a context to providers with `delegate_components!`, //! and implementing a consumer trait directly on a context. //! -//! See docs/concepts/consumer-and-provider-traits.md and -//! docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/concepts/consumer-and-provider-traits.md and +//! cgp-knowledge-base/cgp/reference/macros/delegate_components.md. #![allow(dead_code)] pub mod basic_delegation; diff --git a/crates/tests/cgp-tests/tests/blanket_traits/associated_type.rs b/crates/tests/cgp-tests/tests/blanket_traits/associated_type.rs index 1b0d166e..40766957 100644 --- a/crates/tests/cgp-tests/tests/blanket_traits/associated_type.rs +++ b/crates/tests/cgp-tests/tests/blanket_traits/associated_type.rs @@ -6,7 +6,7 @@ //! //! Snapshot variant: blanket trait re-exporting a supertrait associated type, //! with no bound on the local associated type. -//! See docs/reference/macros/blanket_trait.md. +//! See cgp-knowledge-base/cgp/reference/macros/blanket_trait.md. use cgp_macro_test_util::snapshot_blanket_trait; diff --git a/crates/tests/cgp-tests/tests/blanket_traits/associated_type_bounded.rs b/crates/tests/cgp-tests/tests/blanket_traits/associated_type_bounded.rs index 68208445..0a926b9d 100644 --- a/crates/tests/cgp-tests/tests/blanket_traits/associated_type_bounded.rs +++ b/crates/tests/cgp-tests/tests/blanket_traits/associated_type_bounded.rs @@ -5,7 +5,7 @@ //! //! Snapshot variant: blanket trait re-exporting a supertrait associated type //! with a constraint on the local associated type. -//! See docs/reference/macros/blanket_trait.md. +//! See cgp-knowledge-base/cgp/reference/macros/blanket_trait.md. use cgp_macro_test_util::snapshot_blanket_trait; diff --git a/crates/tests/cgp-tests/tests/blanket_traits/basic.rs b/crates/tests/cgp-tests/tests/blanket_traits/basic.rs index 5c88d969..f0dc87e3 100644 --- a/crates/tests/cgp-tests/tests/blanket_traits/basic.rs +++ b/crates/tests/cgp-tests/tests/blanket_traits/basic.rs @@ -4,7 +4,7 @@ //! is both `Foo` and `Bar` automatically gets `FooBar`. //! //! This is the canonical, minimal expansion snapshot for `#[blanket_trait]`. -//! See docs/reference/macros/blanket_trait.md. +//! See cgp-knowledge-base/cgp/reference/macros/blanket_trait.md. use cgp_macro_test_util::snapshot_blanket_trait; diff --git a/crates/tests/cgp-tests/tests/blanket_traits/with_method.rs b/crates/tests/cgp-tests/tests/blanket_traits/with_method.rs index b09988ad..cc897192 100644 --- a/crates/tests/cgp-tests/tests/blanket_traits/with_method.rs +++ b/crates/tests/cgp-tests/tests/blanket_traits/with_method.rs @@ -3,7 +3,7 @@ //! method body, which delegates to the supertrait methods (`foo`/`bar`). //! //! Snapshot variant: blanket trait with a default method body. -//! See docs/reference/macros/blanket_trait.md. +//! See cgp-knowledge-base/cgp/reference/macros/blanket_trait.md. use cgp_macro_test_util::snapshot_blanket_trait; diff --git a/crates/tests/cgp-tests/tests/blanket_traits_tests.rs b/crates/tests/cgp-tests/tests/blanket_traits_tests.rs index c9f2ace5..5a38588e 100644 --- a/crates/tests/cgp-tests/tests/blanket_traits_tests.rs +++ b/crates/tests/cgp-tests/tests/blanket_traits_tests.rs @@ -6,7 +6,7 @@ //! bounds. This concept OWNS the canonical `#[blanket_trait]` expansion //! snapshots. //! -//! See docs/reference/macros/blanket_trait.md. +//! See cgp-knowledge-base/cgp/reference/macros/blanket_trait.md. #![allow(dead_code)] pub mod blanket_traits; diff --git a/crates/tests/cgp-tests/tests/checking/check_generic.rs b/crates/tests/cgp-tests/tests/checking/check_generic.rs index 0d35ea58..f3bf6140 100644 --- a/crates/tests/cgp-tests/tests/checking/check_generic.rs +++ b/crates/tests/cgp-tests/tests/checking/check_generic.rs @@ -5,8 +5,8 @@ //! component that is itself generic (`BarGetterAtComponent`). This concept owns //! the macro's expansion snapshot. //! -//! See docs/reference/macros/check_components.md and -//! docs/reference/traits/can_use_component.md. +//! See cgp-knowledge-base/cgp/reference/macros/check_components.md and +//! cgp-knowledge-base/cgp/reference/traits/can_use_component.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/checking/check_path_context.rs b/crates/tests/cgp-tests/tests/checking/check_path_context.rs index e1325904..e101492e 100644 --- a/crates/tests/cgp-tests/tests/checking/check_path_context.rs +++ b/crates/tests/cgp-tests/tests/checking/check_path_context.rs @@ -5,8 +5,8 @@ //! by its path without importing it. This concept owns the macro's expansion //! snapshot. //! -//! See docs/reference/macros/check_components.md and -//! docs/implementation/entrypoints/check_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/check_components.md and +//! cgp-knowledge-base/cgp/implementation/entrypoints/check_components.md. use cgp_macro_test_util::snapshot_check_components; diff --git a/crates/tests/cgp-tests/tests/checking/check_providers.rs b/crates/tests/cgp-tests/tests/checking/check_providers.rs index eea2ddc0..52b691e5 100644 --- a/crates/tests/cgp-tests/tests/checking/check_providers.rs +++ b/crates/tests/cgp-tests/tests/checking/check_providers.rs @@ -5,8 +5,8 @@ //! is implemented `for` each provider (here two `UseField` variants), rather than //! `for` the context. This concept owns the macro's expansion snapshot. //! -//! See docs/reference/macros/check_components.md and -//! docs/reference/traits/can_use_component.md. +//! See cgp-knowledge-base/cgp/reference/macros/check_components.md and +//! cgp-knowledge-base/cgp/reference/traits/can_use_component.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/checking/check_trait.rs b/crates/tests/cgp-tests/tests/checking/check_trait.rs index 5108c877..470092c2 100644 --- a/crates/tests/cgp-tests/tests/checking/check_trait.rs +++ b/crates/tests/cgp-tests/tests/checking/check_trait.rs @@ -6,8 +6,8 @@ //! The wiring itself is set up separately with a plain `delegate_components!`. //! This concept owns the macro's expansion snapshot. //! -//! See docs/reference/macros/check_components.md and -//! docs/reference/traits/can_use_component.md. +//! See cgp-knowledge-base/cgp/reference/macros/check_components.md and +//! cgp-knowledge-base/cgp/reference/traits/can_use_component.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/checking/delegate_and_check_basic.rs b/crates/tests/cgp-tests/tests/checking/delegate_and_check_basic.rs index 94904bed..f732a8d6 100644 --- a/crates/tests/cgp-tests/tests/checking/delegate_and_check_basic.rs +++ b/crates/tests/cgp-tests/tests/checking/delegate_and_check_basic.rs @@ -3,8 +3,8 @@ //! `CanUseComponent`-supertraited check trait (here renamed with //! `#[check_trait(...)]`). This concept owns the macro's expansion snapshot. //! -//! See docs/reference/macros/delegate_and_check_components.md and -//! docs/reference/traits/can_use_component.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_and_check_components.md and +//! cgp-knowledge-base/cgp/reference/traits/can_use_component.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_delegate_and_check_components; diff --git a/crates/tests/cgp-tests/tests/checking/delegate_and_check_generic.rs b/crates/tests/cgp-tests/tests/checking/delegate_and_check_generic.rs index 2e549915..6a064848 100644 --- a/crates/tests/cgp-tests/tests/checking/delegate_and_check_generic.rs +++ b/crates/tests/cgp-tests/tests/checking/delegate_and_check_generic.rs @@ -3,8 +3,8 @@ //! the check trait defaults to `__CanUse{Context}`. This concept owns the macro's //! expansion snapshot. //! -//! See docs/reference/macros/delegate_and_check_components.md and -//! docs/reference/traits/can_use_component.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_and_check_components.md and +//! cgp-knowledge-base/cgp/reference/traits/can_use_component.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_delegate_and_check_components; diff --git a/crates/tests/cgp-tests/tests/checking/delegate_and_check_generic_key.rs b/crates/tests/cgp-tests/tests/checking/delegate_and_check_generic_key.rs index 3e91d575..c6e21788 100644 --- a/crates/tests/cgp-tests/tests/checking/delegate_and_check_generic_key.rs +++ b/crates/tests/cgp-tests/tests/checking/delegate_and_check_generic_key.rs @@ -6,8 +6,8 @@ //! itself mentions the key generic and an array key mixing a generic and a //! non-generic component. This concept owns the macro's expansion snapshot. //! -//! See docs/reference/macros/delegate_and_check_components.md and -//! docs/reference/traits/can_use_component.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_and_check_components.md and +//! cgp-knowledge-base/cgp/reference/traits/can_use_component.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/checking/delegate_and_check_params.rs b/crates/tests/cgp-tests/tests/checking/delegate_and_check_params.rs index 9a4db6d7..5d6d2788 100644 --- a/crates/tests/cgp-tests/tests/checking/delegate_and_check_params.rs +++ b/crates/tests/cgp-tests/tests/checking/delegate_and_check_params.rs @@ -4,8 +4,8 @@ //! `#[check_params(...)]` (on the array) is checked in addition to each entry's own //! `#[check_params(...)]`. This concept owns the macro's expansion snapshot. //! -//! See docs/reference/macros/delegate_and_check_components.md and -//! docs/reference/traits/can_use_component.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_and_check_components.md and +//! cgp-knowledge-base/cgp/reference/traits/can_use_component.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/checking_tests.rs b/crates/tests/cgp-tests/tests/checking_tests.rs index 5d6f95d0..e5aebcf5 100644 --- a/crates/tests/cgp-tests/tests/checking_tests.rs +++ b/crates/tests/cgp-tests/tests/checking_tests.rs @@ -9,9 +9,10 @@ //! keys), the `#[check_trait(...)]` name override, the `#[check_providers(...)]` //! form that checks providers directly, and the generic-context/lifetime forms. //! -//! See docs/reference/macros/check_components.md, -//! docs/reference/macros/delegate_and_check_components.md, -//! docs/reference/traits/can_use_component.md, and docs/concepts/check-traits.md. +//! See cgp-knowledge-base/cgp/reference/macros/check_components.md, +//! cgp-knowledge-base/cgp/reference/macros/delegate_and_check_components.md, +//! cgp-knowledge-base/cgp/reference/traits/can_use_component.md, and +//! cgp-knowledge-base/cgp/concepts/check-traits.md. #![allow(dead_code)] pub mod checking; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_generics.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_generics.rs index 646394ff..15c51008 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_generics.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_generics.rs @@ -2,7 +2,7 @@ //! (`CanCall`) mixing an async and a sync method, with a per-variant //! `T: Display` bound on `Foo`. Dispatched over `FooBar`. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args.rs index edcd5cbf..f4219f9d 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args.rs @@ -1,7 +1,7 @@ //! `#[cgp_auto_dispatch]` combined with `#[async_trait]`: a `&self` async method //! with extra by-value arguments, dispatched over `FooBar`. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; use futures::executor::block_on; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args_owned_self.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args_owned_self.rs index a79a2f42..16dd48a1 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args_owned_self.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args_owned_self.rs @@ -2,7 +2,7 @@ //! method taking a `&mut` argument and returning a borrow tied to it, dispatched //! over `FooBar`. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; use futures::executor::block_on; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args_ref.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args_ref.rs index f0d127bf..894ea214 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args_ref.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args_ref.rs @@ -2,7 +2,7 @@ //! method with borrowed arguments and a borrowed return, dispatched over //! `FooBar`. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; use futures::executor::block_on; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_mut_only.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_mut_only.rs index c4e41a60..f2da9123 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_mut_only.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_mut_only.rs @@ -1,7 +1,7 @@ //! `#[cgp_auto_dispatch]` combined with `#[async_trait]`: a `&mut self` async //! method, dispatched over `FooBar`. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; use futures::executor::block_on; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_only.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_only.rs index fa6b7c34..a15add18 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_only.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_only.rs @@ -1,7 +1,7 @@ //! `#[cgp_auto_dispatch]` combined with `#[async_trait]`: a by-value `self` //! async method, dispatched over `FooBar`. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; use futures::executor::block_on; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_ref_only.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_ref_only.rs index 9174d2e9..0ec0ae4e 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_ref_only.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_ref_only.rs @@ -1,7 +1,7 @@ //! `#[cgp_auto_dispatch]` combined with `#[async_trait]`: a `&self` async //! method, dispatched over `FooBar`. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; use futures::executor::block_on; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_generics.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_generics.rs index 14fdae07..26b35e5c 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_generics.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_generics.rs @@ -1,7 +1,7 @@ //! `#[cgp_auto_dispatch]` on a generic trait (`CanCall`), where the per-variant //! impls may add their own bounds on `T` (here `Foo` requires `T: Display`). //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args.rs index 4db67ce9..8a9f1854 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args.rs @@ -1,7 +1,7 @@ //! `#[cgp_auto_dispatch]` on a `&self` method that also takes extra by-value //! arguments, which are forwarded unchanged to the dispatched variant impl. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args_owned_self.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args_owned_self.rs index 08f32446..79eba2df 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args_owned_self.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args_owned_self.rs @@ -1,7 +1,7 @@ //! `#[cgp_auto_dispatch]` on a by-value `self` method that takes a `&mut` //! argument and returns a borrow tied to that argument. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args_ref.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args_ref.rs index fa514275..37ff4a4b 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args_ref.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args_ref.rs @@ -1,7 +1,7 @@ //! `#[cgp_auto_dispatch]` on a `&mut self` method with borrowed arguments and a //! borrowed return, exercising the lifetime handling in the generated handler. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_methods.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_methods.rs index 6ee53604..728522bf 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_methods.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_methods.rs @@ -2,7 +2,7 @@ //! receiver shapes (`&self`, `&mut self`, `self`), all dispatched over the same //! `FooBar` enum. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_mut_only.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_mut_only.rs index 6d585059..b46ae2a8 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_mut_only.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_mut_only.rs @@ -3,7 +3,7 @@ //! The generated handler routes a `&mut FooBar` reference to the matching //! variant impl of `CanCall`. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_only.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_only.rs index 2fbbcf6b..42a7d3cd 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_only.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_only.rs @@ -3,7 +3,7 @@ //! The generated handler routes a `FooBar` value to the `Foo`/`Bar` variant //! impl of `CanCall`, consuming `self`. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_only.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_only.rs index 8fb8c27b..e6e2fec6 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_only.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_only.rs @@ -3,7 +3,7 @@ //! The generated handler routes a `&FooBar` reference to the matching variant //! impl of `CanCall`. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_return_explicit_ref.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_return_explicit_ref.rs index 74768087..f823b645 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_return_explicit_ref.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_return_explicit_ref.rs @@ -4,7 +4,7 @@ //! Companion to `auto_dispatch_self_ref_return_implicit_ref`, which writes the //! same shape with an elided lifetime. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_return_implicit_ref.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_return_implicit_ref.rs index 9e2c6801..66d3125a 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_return_implicit_ref.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_return_implicit_ref.rs @@ -4,7 +4,7 @@ //! Companion to `auto_dispatch_self_ref_return_explicit_ref`, which writes the //! same shape with an explicit lifetime. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_shape.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_shape.rs index 43a9f7ba..bf27f5f8 100644 --- a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_shape.rs +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_shape.rs @@ -3,8 +3,8 @@ //! mutating method (`scale`, `&mut self`). Exercises that a single enum can carry //! several auto-dispatched capabilities at once. //! -//! See docs/reference/macros/cgp_auto_dispatch.md and -//! docs/concepts/dispatching.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md and +//! cgp-knowledge-base/cgp/concepts/dispatching.md. use core::f64::consts::PI; diff --git a/crates/tests/cgp-tests/tests/dispatching/compose.rs b/crates/tests/cgp-tests/tests/dispatching/compose.rs index f598ec0a..af8e35d9 100644 --- a/crates/tests/cgp-tests/tests/dispatching/compose.rs +++ b/crates/tests/cgp-tests/tests/dispatching/compose.rs @@ -10,8 +10,8 @@ //! `#[cgp_new_provider]` is incidental scaffolding here (its expansion is owned by //! `basic_delegation`), so it is written plainly rather than snapshotted. //! -//! See docs/reference/providers/dispatch_combinators.md and -//! docs/reference/components/computer.md. +//! See cgp-knowledge-base/cgp/reference/providers/dispatch_combinators.md and +//! cgp-knowledge-base/cgp/reference/components/computer.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/dispatching/types.rs b/crates/tests/cgp-tests/tests/dispatching/types.rs index 013d827c..57fe6058 100644 --- a/crates/tests/cgp-tests/tests/dispatching/types.rs +++ b/crates/tests/cgp-tests/tests/dispatching/types.rs @@ -6,7 +6,7 @@ //! `Bar`, and rely on `#[cgp_auto_dispatch]` to route a `FooBar` value to the //! matching variant impl. Referenced by siblings via `super::types`. //! -//! See docs/reference/macros/cgp_auto_dispatch.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs b/crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs index 6c155649..4c3f33c3 100644 --- a/crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs +++ b/crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs @@ -11,8 +11,8 @@ //! the `UseDelegate`-table wiring. The `check_components!` scaffolding uses the //! plain macro (checking is owned by another target). //! -//! See docs/reference/providers/use_delegate.md and -//! docs/reference/providers/dispatch_combinators.md. +//! See cgp-knowledge-base/cgp/reference/providers/use_delegate.md and +//! cgp-knowledge-base/cgp/reference/providers/dispatch_combinators.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/dispatching_tests.rs b/crates/tests/cgp-tests/tests/dispatching_tests.rs index 65c8d54f..6436d48f 100644 --- a/crates/tests/cgp-tests/tests/dispatching_tests.rs +++ b/crates/tests/cgp-tests/tests/dispatching_tests.rs @@ -11,10 +11,10 @@ //! scaffolding are written in their plain form (their expansions are pinned in //! their own owning targets). //! -//! See docs/reference/macros/cgp_auto_dispatch.md, -//! docs/reference/providers/use_delegate.md, -//! docs/reference/providers/dispatch_combinators.md, and -//! docs/concepts/dispatching.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_dispatch.md, +//! cgp-knowledge-base/cgp/reference/providers/use_delegate.md, +//! cgp-knowledge-base/cgp/reference/providers/dispatch_combinators.md, and +//! cgp-knowledge-base/cgp/concepts/dispatching.md. #![allow(dead_code)] #![allow(clippy::needless_lifetimes)] diff --git a/crates/tests/cgp-tests/tests/extensible_records/generic_record.rs b/crates/tests/cgp-tests/tests/extensible_records/generic_record.rs index ff3c0b42..25c18036 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/generic_record.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/generic_record.rs @@ -5,7 +5,7 @@ //! parameters and forwards its `where Foo: Clone` bound, so the derive works on //! parameterized records just as on concrete ones. //! -//! See docs/reference/derives/derive_cgp_data.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md. use cgp_macro_test_util::snapshot_derive_cgp_data; diff --git a/crates/tests/cgp-tests/tests/extensible_records/optional_builder.rs b/crates/tests/cgp-tests/tests/extensible_records/optional_builder.rs index 539a6e7a..aa5e9f01 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/optional_builder.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/optional_builder.rs @@ -10,8 +10,8 @@ //! two-field record it exercises; the derive expansion itself is owned by this //! concept. //! -//! See docs/reference/traits/has_builder.md and -//! docs/concepts/extensible-records.md. +//! See cgp-knowledge-base/cgp/reference/traits/has_builder.md and +//! cgp-knowledge-base/cgp/concepts/extensible-records.md. use cgp::extra::field::impls::{ CanFinalizeWithDefault, FinalizeOptional, HasOptionalBuilder, SetOptional, diff --git a/crates/tests/cgp-tests/tests/extensible_records/person_record.rs b/crates/tests/cgp-tests/tests/extensible_records/person_record.rs index 0919eb96..abd7dd36 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/person_record.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/person_record.rs @@ -11,8 +11,8 @@ //! `Person` owns the snapshot here; `Employee`/`EmployeeId` are plain `CgpData` //! fixtures whose expansion is already pinned by `record_derive`. //! -//! See docs/reference/derives/derive_cgp_data.md and -//! docs/reference/traits/has_builder.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md and +//! cgp-knowledge-base/cgp/reference/traits/has_builder.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/extensible_records/point_cast.rs b/crates/tests/cgp-tests/tests/extensible_records/point_cast.rs index 660df89e..9b031d6f 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/point_cast.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/point_cast.rs @@ -6,8 +6,8 @@ //! `#[derive(CgpData)]` expansion for the private `Point2d` (this concept owns //! the derive's expansion); `Point3d`/`Point4d` are plain `CgpData` fixtures. //! -//! See docs/concepts/extensible-records.md and -//! docs/reference/derives/derive_cgp_data.md. +//! See cgp-knowledge-base/cgp/concepts/extensible-records.md and +//! cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md. use cgp::extra::field::impls::CanBuildWithDefault; use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/extensible_records/product_value.rs b/crates/tests/cgp-tests/tests/extensible_records/product_value.rs index bb67fb3c..5085862d 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/product_value.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/product_value.rs @@ -7,8 +7,8 @@ //! builds is exactly the nested `Cons(..)`/`Nil` constructor form, that its type is //! the corresponding `Product!`, and that the empty and trailing-comma forms work. //! -//! See docs/implementation/entrypoints/product.md and -//! docs/reference/macros/product.md. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/product.md and +//! cgp-knowledge-base/cgp/reference/macros/product.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/extensible_records/record_build_from.rs b/crates/tests/cgp-tests/tests/extensible_records/record_build_from.rs index e9568a7a..872f9676 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/record_build_from.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/record_build_from.rs @@ -9,8 +9,8 @@ //! `#[derive(CgpData)]` here is plain scaffolding; its full expansion is pinned //! by `record_derive`. //! -//! See docs/reference/traits/has_builder.md and -//! docs/concepts/extensible-records.md. +//! See cgp-knowledge-base/cgp/reference/traits/has_builder.md and +//! cgp-knowledge-base/cgp/concepts/extensible-records.md. use cgp::core::field::impls::CanBuildFrom; use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/extensible_records/record_build_with_handlers.rs b/crates/tests/cgp-tests/tests/extensible_records/record_build_with_handlers.rs index 6ecef03d..24025d24 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/record_build_with_handlers.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/record_build_with_handlers.rs @@ -11,8 +11,8 @@ //! (it only fixes the context's error type); its expansion is owned by //! `basic_delegation`. //! -//! See docs/concepts/extensible-records.md and -//! docs/reference/traits/has_builder.md. +//! See cgp-knowledge-base/cgp/concepts/extensible-records.md and +//! cgp-knowledge-base/cgp/reference/traits/has_builder.md. use core::convert::Infallible; use std::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/extensible_records/record_derive.rs b/crates/tests/cgp-tests/tests/extensible_records/record_derive.rs index 35252c09..1acc55d6 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/record_derive.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/record_derive.rs @@ -11,8 +11,8 @@ //! The runtime test exercises the builder: start from `builder()`, set each //! field with `build_field`, then `finalize_build` once every field is present. //! -//! See docs/reference/derives/derive_cgp_data.md and -//! docs/reference/traits/has_builder.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md and +//! cgp-knowledge-base/cgp/reference/traits/has_builder.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/extensible_records/record_lifetime.rs b/crates/tests/cgp-tests/tests/extensible_records/record_lifetime.rs index d2a283ec..2917c2ad 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/record_lifetime.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/record_lifetime.rs @@ -9,7 +9,7 @@ //! by `record_derive` and `struct_generic_lifetime`, so this only guards that //! the builder round-trips at compile and run time on a borrowed record. //! -//! See docs/reference/derives/derive_cgp_data.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/extensible_records/struct_generic.rs b/crates/tests/cgp-tests/tests/extensible_records/struct_generic.rs index 8decff42..50cca9ae 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/struct_generic.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/struct_generic.rs @@ -4,7 +4,7 @@ //! `where Name: Display` bound, so the field-list derive works on parameterized //! structs. The derive expansion is owned by this concept. //! -//! See docs/reference/derives/derive_has_fields.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_fields.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/extensible_records/struct_generic_lifetime.rs b/crates/tests/cgp-tests/tests/extensible_records/struct_generic_lifetime.rs index 93dc0219..ce6cd5e8 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/struct_generic_lifetime.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/struct_generic_lifetime.rs @@ -5,7 +5,7 @@ //! separate `'__a` borrow on top (`&'__a &'a Name`). The derive expansion is //! owned by this concept. //! -//! See docs/reference/derives/derive_has_fields.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_fields.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/extensible_records/struct_single_named_field.rs b/crates/tests/cgp-tests/tests/extensible_records/struct_single_named_field.rs index 4b8c8ba1..6841487d 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/struct_single_named_field.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/struct_single_named_field.rs @@ -7,8 +7,8 @@ //! access and no builder. This is the reference snapshot for that derive on a //! struct; the other `struct_*` files reuse it for the remaining field shapes. //! -//! See docs/reference/derives/derive_has_fields.md and -//! docs/reference/traits/has_fields.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_fields.md and +//! cgp-knowledge-base/cgp/reference/traits/has_fields.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_derive_has_fields; diff --git a/crates/tests/cgp-tests/tests/extensible_records/struct_single_unnamed_field.rs b/crates/tests/cgp-tests/tests/extensible_records/struct_single_unnamed_field.rs index d811544b..9672f0c0 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/struct_single_unnamed_field.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/struct_single_unnamed_field.rs @@ -4,7 +4,7 @@ //! directly (not a `Cons`/`Nil` list), and `from_fields`/`to_fields` pass the //! single value straight through. The derive expansion is owned by this concept. //! -//! See docs/reference/derives/derive_has_fields.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_fields.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_derive_has_fields; diff --git a/crates/tests/cgp-tests/tests/extensible_records/struct_tuple_fields.rs b/crates/tests/cgp-tests/tests/extensible_records/struct_tuple_fields.rs index d1e5e9b3..4c5c5a60 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/struct_tuple_fields.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/struct_tuple_fields.rs @@ -4,7 +4,7 @@ //! rather than treating it as a bare newtype, producing the usual `Cons`/`Nil` //! spine. The derive expansion is owned by this concept. //! -//! See docs/reference/derives/derive_has_fields.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_fields.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_derive_has_fields; diff --git a/crates/tests/cgp-tests/tests/extensible_records/struct_two_named_fields.rs b/crates/tests/cgp-tests/tests/extensible_records/struct_two_named_fields.rs index b683753e..2e298576 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/struct_two_named_fields.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/struct_two_named_fields.rs @@ -4,7 +4,7 @@ //! `from_fields`/`to_fields` round-trip. The derive expansion is owned by this //! concept; `struct_single_named_field` pins the canonical reference. //! -//! See docs/reference/derives/derive_has_fields.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_fields.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_derive_has_fields; diff --git a/crates/tests/cgp-tests/tests/extensible_records/tuple_record.rs b/crates/tests/cgp-tests/tests/extensible_records/tuple_record.rs index fd29fa22..52a45577 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/tuple_record.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/tuple_record.rs @@ -5,7 +5,7 @@ //! builder — is keyed by index. Otherwise the expansion mirrors the named-field //! case pinned by `record_derive`. //! -//! See docs/reference/derives/derive_cgp_data.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md. use cgp_macro_test_util::snapshot_derive_cgp_data; diff --git a/crates/tests/cgp-tests/tests/extensible_records_tests.rs b/crates/tests/cgp-tests/tests/extensible_records_tests.rs index d0fe75b9..8a653e3d 100644 --- a/crates/tests/cgp-tests/tests/extensible_records_tests.rs +++ b/crates/tests/cgp-tests/tests/extensible_records_tests.rs @@ -9,10 +9,10 @@ //! derives on structs, and exercises named, tuple, generic, optional, and cast //! variations. //! -//! See docs/reference/derives/derive_cgp_data.md, -//! docs/reference/derives/derive_has_fields.md, -//! docs/reference/traits/has_builder.md, and -//! docs/concepts/extensible-records.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md, +//! cgp-knowledge-base/cgp/reference/derives/derive_has_fields.md, +//! cgp-knowledge-base/cgp/reference/traits/has_builder.md, and +//! cgp-knowledge-base/cgp/concepts/extensible-records.md. #![allow(dead_code)] #![allow(clippy::disallowed_names)] diff --git a/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data.rs b/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data.rs index 4ab1a1c8..c215ea9a 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data.rs @@ -10,9 +10,9 @@ //! (`CanUpcast`/`CanDowncast`) between enum shapes; the auxiliary enums are //! plain derives, since the primary snapshot already pins the expansion. //! -//! See docs/reference/derives/derive_cgp_data.md, -//! docs/reference/derives/derive_from_variant.md, and -//! docs/reference/derives/derive_extract_field.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md, +//! cgp-knowledge-base/cgp/reference/derives/derive_from_variant.md, and +//! cgp-knowledge-base/cgp/reference/derives/derive_extract_field.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_empty.rs b/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_empty.rs index 99662fba..1e6c2315 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_empty.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_empty.rs @@ -11,8 +11,8 @@ //! This concept owns the variant expansion of `#[derive(CgpData)]`; this file is //! the empty-enum snapshot. //! -//! See docs/reference/derives/derive_cgp_data.md and -//! docs/reference/derives/derive_extract_field.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md and +//! cgp-knowledge-base/cgp/reference/derives/derive_extract_field.md. use cgp_macro_test_util::snapshot_derive_cgp_data; diff --git a/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_generic.rs b/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_generic.rs index 875c415e..be7942ce 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_generic.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_generic.rs @@ -7,7 +7,7 @@ //! This concept owns the variant expansion of `#[derive(CgpData)]`; this file is //! the generic-enum snapshot. //! -//! See docs/reference/derives/derive_cgp_data.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_lifetime.rs b/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_lifetime.rs index 23e71072..08cb3c7f 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_lifetime.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_lifetime.rs @@ -9,8 +9,8 @@ //! extractor expansion shape, so this only exercises the corner case at compile //! and run time. //! -//! See docs/reference/derives/derive_cgp_data.md and -//! docs/reference/derives/derive_extract_field.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md and +//! cgp-knowledge-base/cgp/reference/derives/derive_extract_field.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_shape.rs b/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_shape.rs index b7cd45a5..70c6c460 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_shape.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_shape.rs @@ -9,8 +9,8 @@ //! the widening/narrowing casts against enums of different arity //! (`ShapePlus`/`TriangleOnly`), which are plain derives. //! -//! See docs/reference/derives/derive_cgp_data.md and -//! docs/reference/derives/derive_extract_field.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md and +//! cgp-knowledge-base/cgp/reference/derives/derive_extract_field.md. use core::marker::PhantomData; use std::f64::consts::PI; diff --git a/crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum.rs b/crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum.rs index 235ec88d..c5c96c9e 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum.rs @@ -7,7 +7,8 @@ //! //! This concept owns the enum expansion of the `HasFields` derive. //! -//! See docs/reference/derives/derive_has_fields.md and docs/reference/macros/sum.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_fields.md and +//! cgp-knowledge-base/cgp/reference/macros/sum.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_derive_has_fields; diff --git a/crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum_generic.rs b/crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum_generic.rs index 2758bbc1..6723890d 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum_generic.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum_generic.rs @@ -6,7 +6,7 @@ //! This concept owns the enum expansion of the `HasFields` derive; this file //! pins the generic-enum variant of that expansion. //! -//! See docs/reference/derives/derive_has_fields.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_fields.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_derive_has_fields; diff --git a/crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch.rs b/crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch.rs index 522b8e5a..a700894f 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch.rs @@ -10,7 +10,7 @@ //! owned by other concepts, so those macros appear plainly here; the derives are //! plain scaffolding. This file exercises the variant side of owned dispatch. //! -//! See docs/concepts/extensible-variants.md. +//! See cgp-knowledge-base/cgp/concepts/extensible-variants.md. use core::marker::PhantomData; use std::f64::consts::PI; diff --git a/crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch_ref.rs b/crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch_ref.rs index 5304ea07..508a7b9e 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch_ref.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch_ref.rs @@ -9,7 +9,7 @@ //! The dispatch combinators are owned elsewhere; the derives here are plain //! scaffolding. This file exercises the by-reference / by-mut variant side. //! -//! See docs/concepts/extensible-variants.md. +//! See cgp-knowledge-base/cgp/concepts/extensible-variants.md. use std::f64::consts::PI; diff --git a/crates/tests/cgp-tests/tests/extensible_variants/sum_macro.rs b/crates/tests/cgp-tests/tests/extensible_variants/sum_macro.rs index d07d8a6a..bc7c44d3 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants/sum_macro.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants/sum_macro.rs @@ -8,7 +8,8 @@ //! `#[derive(HasFields)]`, and the spine every extractor/cast in this concept //! walks. //! -//! See docs/reference/macros/sum.md and docs/reference/types/either.md. +//! See cgp-knowledge-base/cgp/reference/macros/sum.md and +//! cgp-knowledge-base/cgp/reference/types/either.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/extensible_variants/variant_dispatch.rs b/crates/tests/cgp-tests/tests/extensible_variants/variant_dispatch.rs index c91eee19..db0a0610 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants/variant_dispatch.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants/variant_dispatch.rs @@ -9,7 +9,7 @@ //! to plain macros. This file exercises the *variant* side end-to-end: matching //! by field, by value (ref), and via casts. //! -//! See docs/concepts/extensible-variants.md. +//! See cgp-knowledge-base/cgp/concepts/extensible-variants.md. use core::convert::Infallible; use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/extensible_variants_tests.rs b/crates/tests/cgp-tests/tests/extensible_variants_tests.rs index e1f35a3c..5b0e481e 100644 --- a/crates/tests/cgp-tests/tests/extensible_variants_tests.rs +++ b/crates/tests/cgp-tests/tests/extensible_variants_tests.rs @@ -11,9 +11,11 @@ //! The dual concept for structs is `extensible_records`; `field_access` owns //! the `#[derive(HasField)]` struct derive. //! -//! See docs/concepts/extensible-variants.md, docs/reference/derives/derive_cgp_data.md, -//! docs/reference/derives/derive_from_variant.md, docs/reference/derives/derive_extract_field.md, -//! and docs/reference/macros/sum.md. +//! See cgp-knowledge-base/cgp/concepts/extensible-variants.md, +//! cgp-knowledge-base/cgp/reference/derives/derive_cgp_data.md, +//! cgp-knowledge-base/cgp/reference/derives/derive_from_variant.md, +//! cgp-knowledge-base/cgp/reference/derives/derive_extract_field.md, and +//! cgp-knowledge-base/cgp/reference/macros/sum.md. #![allow(dead_code)] pub mod extensible_variants; diff --git a/crates/tests/cgp-tests/tests/field_access/chain.rs b/crates/tests/cgp-tests/tests/field_access/chain.rs index 731548c6..fb4492b0 100644 --- a/crates/tests/cgp-tests/tests/field_access/chain.rs +++ b/crates/tests/cgp-tests/tests/field_access/chain.rs @@ -4,8 +4,8 @@ //! `#[derive(HasField)]` snapshots are the point of the test; the `ChainGetters` //! wiring is behavioral scaffolding. //! -//! See docs/reference/derives/derive_has_field.md and -//! docs/reference/traits/has_field.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_field.md and +//! cgp-knowledge-base/cgp/reference/traits/has_field.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/field_access/chain_deeply_nested.rs b/crates/tests/cgp-tests/tests/field_access/chain_deeply_nested.rs index 9b4bfcc2..e0853b7e 100644 --- a/crates/tests/cgp-tests/tests/field_access/chain_deeply_nested.rs +++ b/crates/tests/cgp-tests/tests/field_access/chain_deeply_nested.rs @@ -8,8 +8,8 @@ //! expansions are owned by the `getters` and `checking` targets — so they are //! written as the plain macros. //! -//! See docs/reference/derives/derive_has_field.md and -//! docs/reference/traits/has_field.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_field.md and +//! cgp-knowledge-base/cgp/reference/traits/has_field.md. use cgp::core::field::impls::ChainGetters; use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/field_access/chain_inner_life.rs b/crates/tests/cgp-tests/tests/field_access/chain_inner_life.rs index b0601cce..83b62f89 100644 --- a/crates/tests/cgp-tests/tests/field_access/chain_inner_life.rs +++ b/crates/tests/cgp-tests/tests/field_access/chain_inner_life.rs @@ -2,7 +2,7 @@ //! (`Inner<'a>` holding `&'a String`): both derives lift the lifetime onto their //! impls, and `ChainGetters` composes them to read the borrowed name. //! -//! See docs/reference/derives/derive_has_field.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_field.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/field_access/chain_outer_life.rs b/crates/tests/cgp-tests/tests/field_access/chain_outer_life.rs index be6ae6c4..1fe99729 100644 --- a/crates/tests/cgp-tests/tests/field_access/chain_outer_life.rs +++ b/crates/tests/cgp-tests/tests/field_access/chain_outer_life.rs @@ -2,7 +2,7 @@ //! (`inner: &'a Inner`): the derive lifts the outer lifetime onto its impls, //! and `ChainGetters` still resolves `outer.inner.name` across the borrow. //! -//! See docs/reference/derives/derive_has_field.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_field.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/field_access/index.rs b/crates/tests/cgp-tests/tests/field_access/index.rs index d14cb815..d156ab63 100644 --- a/crates/tests/cgp-tests/tests/field_access/index.rs +++ b/crates/tests/cgp-tests/tests/field_access/index.rs @@ -2,8 +2,8 @@ //! name, so the derive keys each one by the type-level number `Index` //! (`Index<0>` for `.0`, `Index<1>` for `.1`) instead of a `Symbol!`. //! -//! See docs/reference/derives/derive_has_field.md and -//! docs/reference/types/index.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_field.md and +//! cgp-knowledge-base/cgp/reference/types/index.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_derive_has_field; diff --git a/crates/tests/cgp-tests/tests/field_access/index_display.rs b/crates/tests/cgp-tests/tests/field_access/index_display.rs index d1626af6..18808daa 100644 --- a/crates/tests/cgp-tests/tests/field_access/index_display.rs +++ b/crates/tests/cgp-tests/tests/field_access/index_display.rs @@ -1,7 +1,7 @@ //! Runtime behavior of the `Index` type-level number tag: an `Index` //! value `Display`s as its underlying number. //! -//! See docs/reference/types/index.md. +//! See cgp-knowledge-base/cgp/reference/types/index.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/field_access/lifetime_field.rs b/crates/tests/cgp-tests/tests/field_access/lifetime_field.rs index 8a4acadd..0f4602f9 100644 --- a/crates/tests/cgp-tests/tests/field_access/lifetime_field.rs +++ b/crates/tests/cgp-tests/tests/field_access/lifetime_field.rs @@ -2,7 +2,7 @@ //! lifts the struct's lifetime onto the generated impls and keeps the field's //! borrowed type (`&'a str`) as the `HasField::Value`. //! -//! See docs/reference/derives/derive_has_field.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_field.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/field_access/raw_ident.rs b/crates/tests/cgp-tests/tests/field_access/raw_ident.rs index 62c8436b..38a86bc8 100644 --- a/crates/tests/cgp-tests/tests/field_access/raw_ident.rs +++ b/crates/tests/cgp-tests/tests/field_access/raw_ident.rs @@ -3,8 +3,8 @@ //! stripped — so the field is addressable by the same symbol `Symbol!("type")` //! produces, while the accessor body still borrows `&self.r#type`. //! -//! See docs/reference/derives/derive_has_field.md and -//! docs/reference/macros/symbol.md. +//! See cgp-knowledge-base/cgp/reference/derives/derive_has_field.md and +//! cgp-knowledge-base/cgp/reference/macros/symbol.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_derive_has_field; diff --git a/crates/tests/cgp-tests/tests/field_access/symbol.rs b/crates/tests/cgp-tests/tests/field_access/symbol.rs index 2eccbd56..c32c6a17 100644 --- a/crates/tests/cgp-tests/tests/field_access/symbol.rs +++ b/crates/tests/cgp-tests/tests/field_access/symbol.rs @@ -2,7 +2,8 @@ //! `Display`s as the string it encodes, and its `StaticString::VALUE` recovers //! the original literal — including the empty string and multi-byte Unicode. //! -//! See docs/reference/macros/symbol.md and docs/reference/traits/has_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/symbol.md and +//! cgp-knowledge-base/cgp/reference/traits/has_field.md. use cgp::core::field::traits::StaticString; use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/field_access_tests.rs b/crates/tests/cgp-tests/tests/field_access_tests.rs index fd791f66..16115e52 100644 --- a/crates/tests/cgp-tests/tests/field_access_tests.rs +++ b/crates/tests/cgp-tests/tests/field_access_tests.rs @@ -9,9 +9,10 @@ //! and nested/chained field access, plus the runtime behavior of the `Symbol!` //! and `Index` tags themselves (`Display` and `StaticString`). //! -//! See docs/reference/traits/has_field.md, -//! docs/reference/derives/derive_has_field.md, -//! docs/reference/macros/symbol.md and docs/reference/types/index.md. +//! See cgp-knowledge-base/cgp/reference/traits/has_field.md, +//! cgp-knowledge-base/cgp/reference/derives/derive_has_field.md, +//! cgp-knowledge-base/cgp/reference/macros/symbol.md and +//! cgp-knowledge-base/cgp/reference/types/index.md. #![allow(dead_code)] pub mod field_access; diff --git a/crates/tests/cgp-tests/tests/generic_components/component_const.rs b/crates/tests/cgp-tests/tests/generic_components/component_const.rs index fbe8f78f..09d383ba 100644 --- a/crates/tests/cgp-tests/tests/generic_components/component_const.rs +++ b/crates/tests/cgp-tests/tests/generic_components/component_const.rs @@ -8,7 +8,7 @@ //! plainly (its expansion is owned by the `basic_delegation` / `checking` //! concepts). //! -//! See docs/reference/macros/cgp_component.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_component.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_provider; diff --git a/crates/tests/cgp-tests/tests/generic_components/component_generic_const.rs b/crates/tests/cgp-tests/tests/generic_components/component_generic_const.rs index 4ff0cd3f..08f9b008 100644 --- a/crates/tests/cgp-tests/tests/generic_components/component_generic_const.rs +++ b/crates/tests/cgp-tests/tests/generic_components/component_generic_const.rs @@ -9,7 +9,7 @@ //! `check_components!` wiring is written plainly (owned by the `abstract_types`, //! `basic_delegation`, and `checking` concepts). //! -//! See docs/reference/macros/cgp_component.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_component.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_provider; diff --git a/crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs b/crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs index 3776582a..f5ca71ab 100644 --- a/crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs +++ b/crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs @@ -9,9 +9,10 @@ //! `delegate_components!`, and `check_components!` wiring below is written plainly //! (its expansion is owned by the `basic_delegation` and `checking` concepts). //! -//! See docs/implementation/entrypoints/cgp_component.md (Snapshots) for this -//! lifetime-and-type-parameter variant, and docs/reference/macros/cgp_component.md -//! and docs/reference/types/life.md for the user-facing semantics. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_component.md (Snapshots) for this +//! lifetime-and-type-parameter variant, and +//! cgp-knowledge-base/cgp/reference/macros/cgp_component.md and +//! cgp-knowledge-base/cgp/reference/types/life.md for the user-facing semantics. use cgp::prelude::*; use cgp_macro_test_util::{snapshot_cgp_component, snapshot_cgp_provider}; diff --git a/crates/tests/cgp-tests/tests/generic_components/component_type_param.rs b/crates/tests/cgp-tests/tests/generic_components/component_type_param.rs index db9d89d4..33cf6945 100644 --- a/crates/tests/cgp-tests/tests/generic_components/component_type_param.rs +++ b/crates/tests/cgp-tests/tests/generic_components/component_type_param.rs @@ -8,8 +8,8 @@ //! and `check_components!` wiring below is written plainly (owned by the //! `basic_delegation` and `checking` concepts). //! -//! See docs/implementation/entrypoints/cgp_component.md (Snapshots) for this -//! type-parameter variant, and docs/reference/macros/cgp_component.md for the +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_component.md (Snapshots) for this +//! type-parameter variant, and cgp-knowledge-base/cgp/reference/macros/cgp_component.md for the //! user-facing semantics. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/generic_components/fn_generic_param.rs b/crates/tests/cgp-tests/tests/generic_components/fn_generic_param.rs index 4a5006fc..9d058c70 100644 --- a/crates/tests/cgp-tests/tests/generic_components/fn_generic_param.rs +++ b/crates/tests/cgp-tests/tests/generic_components/fn_generic_param.rs @@ -5,7 +5,7 @@ //! `#[implicit]` arguments are pulled from the context's `width`/`height` fields. //! This is the reference snapshot for a `#[cgp_fn]` carrying a type parameter. //! -//! See docs/reference/macros/cgp_fn.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md. use std::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/generic_components/fn_impl_generics.rs b/crates/tests/cgp-tests/tests/generic_components/fn_impl_generics.rs index 1e00cb9b..1fdfcb2b 100644 --- a/crates/tests/cgp-tests/tests/generic_components/fn_impl_generics.rs +++ b/crates/tests/cgp-tests/tests/generic_components/fn_impl_generics.rs @@ -6,7 +6,7 @@ //! carries `Name: Display`. A second `#[cgp_fn]` (`test_greet`) then imports the //! `Greet` capability with `#[uses(...)]` and drives the runtime assertion. //! -//! See docs/reference/macros/cgp_fn.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/generic_components_tests.rs b/crates/tests/cgp-tests/tests/generic_components_tests.rs index 289cad2b..73a81166 100644 --- a/crates/tests/cgp-tests/tests/generic_components_tests.rs +++ b/crates/tests/cgp-tests/tests/generic_components_tests.rs @@ -8,8 +8,10 @@ //! `IsProviderFor` params tuple, and how const generics flow onto the generated //! provider trait and its providers. //! -//! See docs/reference/macros/cgp_component.md, docs/reference/macros/cgp_fn.md, -//! docs/reference/types/life.md, and docs/concepts/higher-order-providers.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_component.md, +//! cgp-knowledge-base/cgp/reference/macros/cgp_fn.md, +//! cgp-knowledge-base/cgp/reference/types/life.md, and +//! cgp-knowledge-base/cgp/concepts/higher-order-providers.md. #![allow(dead_code)] pub mod generic_components; diff --git a/crates/tests/cgp-tests/tests/getters/abstract_type_extend.rs b/crates/tests/cgp-tests/tests/getters/abstract_type_extend.rs index 435d9144..441ccafa 100644 --- a/crates/tests/cgp-tests/tests/getters/abstract_type_extend.rs +++ b/crates/tests/cgp-tests/tests/getters/abstract_type_extend.rs @@ -4,8 +4,9 @@ //! The `#[cgp_type]` scaffolding is written plainly here — its expansion is owned //! by the `abstract_types` concept. //! -//! See docs/reference/macros/cgp_getter.md, docs/reference/macros/cgp_auto_getter.md, -//! and docs/reference/providers/use_type.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md, +//! cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md, and +//! cgp-knowledge-base/cgp/reference/providers/use_type.md. use cgp::prelude::*; use cgp_macro_test_util::{snapshot_cgp_auto_getter, snapshot_cgp_getter}; diff --git a/crates/tests/cgp-tests/tests/getters/abstract_type_use_type.rs b/crates/tests/cgp-tests/tests/getters/abstract_type_use_type.rs index d7db5baa..1e8e97fd 100644 --- a/crates/tests/cgp-tests/tests/getters/abstract_type_use_type.rs +++ b/crates/tests/cgp-tests/tests/getters/abstract_type_use_type.rs @@ -5,8 +5,9 @@ //! variant are pinned. The `#[cgp_type]` scaffolding is written plainly here — //! its expansion is owned by the `abstract_types` concept. //! -//! See docs/reference/macros/cgp_getter.md, docs/reference/macros/cgp_auto_getter.md, -//! and docs/reference/providers/use_type.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md, +//! cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md, and +//! cgp-knowledge-base/cgp/reference/providers/use_type.md. use cgp::prelude::*; use cgp_macro_test_util::{snapshot_cgp_auto_getter, snapshot_cgp_getter}; diff --git a/crates/tests/cgp-tests/tests/getters/assoc_type_auto_getter.rs b/crates/tests/cgp-tests/tests/getters/assoc_type_auto_getter.rs index 7d3d3262..b4fd5a11 100644 --- a/crates/tests/cgp-tests/tests/getters/assoc_type_auto_getter.rs +++ b/crates/tests/cgp-tests/tests/getters/assoc_type_auto_getter.rs @@ -2,7 +2,7 @@ //! associated type used as the return type: the type is inferred from the field //! and the trait bound (`Display`) is carried onto the generated blanket impl. //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/getters/assoc_type_getter.rs b/crates/tests/cgp-tests/tests/getters/assoc_type_getter.rs index 7456c4e2..c8fe43f6 100644 --- a/crates/tests/cgp-tests/tests/getters/assoc_type_getter.rs +++ b/crates/tests/cgp-tests/tests/getters/assoc_type_getter.rs @@ -4,7 +4,8 @@ //! impls that carry the associated type through. The context then binds the //! source field by wiring the getter to `UseField`. //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use core::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/getters/assoc_type_self_referential.rs b/crates/tests/cgp-tests/tests/getters/assoc_type_self_referential.rs index 81af7166..fdeacd7a 100644 --- a/crates/tests/cgp-tests/tests/getters/assoc_type_self_referential.rs +++ b/crates/tests/cgp-tests/tests/getters/assoc_type_self_referential.rs @@ -3,7 +3,8 @@ //! field: `Person::name()` reads the `first_name` field via //! `UseField`. //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/getters/assoc_type_self_referential_auto.rs b/crates/tests/cgp-tests/tests/getters/assoc_type_self_referential_auto.rs index 53592b58..28771f92 100644 --- a/crates/tests/cgp-tests/tests/getters/assoc_type_self_referential_auto.rs +++ b/crates/tests/cgp-tests/tests/getters/assoc_type_self_referential_auto.rs @@ -2,7 +2,7 @@ //! (`Scalar: Mul`): the bound survives onto the generated //! blanket impl with `Self::Scalar` rewritten to the inferred type parameter. //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use core::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/getters/auto_getter_generic.rs b/crates/tests/cgp-tests/tests/getters/auto_getter_generic.rs index 57c5e6e1..81c43447 100644 --- a/crates/tests/cgp-tests/tests/getters/auto_getter_generic.rs +++ b/crates/tests/cgp-tests/tests/getters/auto_getter_generic.rs @@ -2,7 +2,7 @@ //! by a `PhantomData` tag and reads the `foo` field, so the same getter //! works for any field type inferred from the generic argument. //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_auto_getter; diff --git a/crates/tests/cgp-tests/tests/getters/clone.rs b/crates/tests/cgp-tests/tests/getters/clone.rs index 1a731578..1c87e94d 100644 --- a/crates/tests/cgp-tests/tests/getters/clone.rs +++ b/crates/tests/cgp-tests/tests/getters/clone.rs @@ -4,7 +4,8 @@ //! plainly here (their expansions are owned by the `abstract_types` and //! `basic_delegation` concepts). //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_getter; diff --git a/crates/tests/cgp-tests/tests/getters/clone_auto.rs b/crates/tests/cgp-tests/tests/getters/clone_auto.rs index a69519a7..660b1cc4 100644 --- a/crates/tests/cgp-tests/tests/getters/clone_auto.rs +++ b/crates/tests/cgp-tests/tests/getters/clone_auto.rs @@ -4,7 +4,7 @@ //! `delegate_components!` wiring are written plainly here (their expansions are //! owned by the `abstract_types` and `basic_delegation` concepts). //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_auto_getter; diff --git a/crates/tests/cgp-tests/tests/getters/mref.rs b/crates/tests/cgp-tests/tests/getters/mref.rs index 7cecb7eb..16d10252 100644 --- a/crates/tests/cgp-tests/tests/getters/mref.rs +++ b/crates/tests/cgp-tests/tests/getters/mref.rs @@ -2,7 +2,8 @@ //! the getter reads a `String` field and wraps the borrow in `MRef::Ref`. The //! context binds the source field via `UseField`. //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_getter; diff --git a/crates/tests/cgp-tests/tests/getters/mref_auto.rs b/crates/tests/cgp-tests/tests/getters/mref_auto.rs index c3545a1f..c955c4cb 100644 --- a/crates/tests/cgp-tests/tests/getters/mref_auto.rs +++ b/crates/tests/cgp-tests/tests/getters/mref_auto.rs @@ -2,7 +2,7 @@ //! value): the blanket impl reads a `String` field named after the method and //! wraps the borrow in `MRef::Ref` without wiring. //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_auto_getter; diff --git a/crates/tests/cgp-tests/tests/getters/mut_getter.rs b/crates/tests/cgp-tests/tests/getters/mut_getter.rs index ded4a03c..c063523d 100644 --- a/crates/tests/cgp-tests/tests/getters/mut_getter.rs +++ b/crates/tests/cgp-tests/tests/getters/mut_getter.rs @@ -5,7 +5,8 @@ //! `FieldGetter`. The context binds a differently-named `value` field by wiring //! to `UseField`. //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_getter; diff --git a/crates/tests/cgp-tests/tests/getters/mut_option_auto.rs b/crates/tests/cgp-tests/tests/getters/mut_option_auto.rs index 13951cad..d763c32e 100644 --- a/crates/tests/cgp-tests/tests/getters/mut_option_auto.rs +++ b/crates/tests/cgp-tests/tests/getters/mut_option_auto.rs @@ -2,7 +2,7 @@ //! the blanket impl reads an `Option` field mutably through `get_field_mut` //! and calls `.as_mut()`, the mutable mirror of the shared `Option<&T>` getter. //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_auto_getter; diff --git a/crates/tests/cgp-tests/tests/getters/mut_slice.rs b/crates/tests/cgp-tests/tests/getters/mut_slice.rs index 26fd9b4a..7869b1b4 100644 --- a/crates/tests/cgp-tests/tests/getters/mut_slice.rs +++ b/crates/tests/cgp-tests/tests/getters/mut_slice.rs @@ -7,7 +7,8 @@ //! getter's `FieldGetter`/`AsRef<[u32]>`. This pins the non-auto getter's //! mutable-slice path, which the `#[cgp_auto_getter]` snapshots do not exercise. //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_getter; diff --git a/crates/tests/cgp-tests/tests/getters/mut_slice_auto.rs b/crates/tests/cgp-tests/tests/getters/mut_slice_auto.rs index fd7fa2b2..79aa4ac4 100644 --- a/crates/tests/cgp-tests/tests/getters/mut_slice_auto.rs +++ b/crates/tests/cgp-tests/tests/getters/mut_slice_auto.rs @@ -3,7 +3,7 @@ //! `Vec`) mutably through `get_field_mut` and calls `.as_mut()`, the mutable //! mirror of the shared `&[T]` slice getter. //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_auto_getter; diff --git a/crates/tests/cgp-tests/tests/getters/mut_str_auto.rs b/crates/tests/cgp-tests/tests/getters/mut_str_auto.rs index 82405834..78aea201 100644 --- a/crates/tests/cgp-tests/tests/getters/mut_str_auto.rs +++ b/crates/tests/cgp-tests/tests/getters/mut_str_auto.rs @@ -3,7 +3,7 @@ //! by `HasFieldMut`) and calls `.as_mut_str()`, so a `String` field can be exposed //! as a mutable `&mut str`. //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_auto_getter; diff --git a/crates/tests/cgp-tests/tests/getters/non_self.rs b/crates/tests/cgp-tests/tests/getters/non_self.rs index 95b10fe3..c977e031 100644 --- a/crates/tests/cgp-tests/tests/getters/non_self.rs +++ b/crates/tests/cgp-tests/tests/getters/non_self.rs @@ -5,7 +5,8 @@ //! (their expansions are owned by the `abstract_types` and `basic_delegation` //! concepts). //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_getter; diff --git a/crates/tests/cgp-tests/tests/getters/non_self_auto.rs b/crates/tests/cgp-tests/tests/getters/non_self_auto.rs index 7a427a22..f703fb4d 100644 --- a/crates/tests/cgp-tests/tests/getters/non_self_auto.rs +++ b/crates/tests/cgp-tests/tests/getters/non_self_auto.rs @@ -5,7 +5,7 @@ //! `delegate_components!` wiring are written plainly here (their expansions are //! owned by the `abstract_types` and `basic_delegation` concepts). //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_auto_getter; diff --git a/crates/tests/cgp-tests/tests/getters/option.rs b/crates/tests/cgp-tests/tests/getters/option.rs index 8c45ea04..71fd2cea 100644 --- a/crates/tests/cgp-tests/tests/getters/option.rs +++ b/crates/tests/cgp-tests/tests/getters/option.rs @@ -2,7 +2,8 @@ //! `Option` field and calls `.as_ref()`, converting `&Option` //! into `Option<&String>`. The context binds the source field via `UseField`. //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_getter; diff --git a/crates/tests/cgp-tests/tests/getters/option_auto.rs b/crates/tests/cgp-tests/tests/getters/option_auto.rs index 9c002750..eb4a8de0 100644 --- a/crates/tests/cgp-tests/tests/getters/option_auto.rs +++ b/crates/tests/cgp-tests/tests/getters/option_auto.rs @@ -2,7 +2,7 @@ //! `Option` field named after the method and calls `.as_ref()`, //! converting `&Option` into `Option<&String>` without wiring. //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_auto_getter; diff --git a/crates/tests/cgp-tests/tests/getters/option_str_auto.rs b/crates/tests/cgp-tests/tests/getters/option_str_auto.rs index 36a2048f..c692f87e 100644 --- a/crates/tests/cgp-tests/tests/getters/option_str_auto.rs +++ b/crates/tests/cgp-tests/tests/getters/option_str_auto.rs @@ -3,7 +3,7 @@ //! converting `&Option` into `Option<&str>` — the `&str`/`String` special //! case composed with the option case. //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_auto_getter; diff --git a/crates/tests/cgp-tests/tests/getters/slice.rs b/crates/tests/cgp-tests/tests/getters/slice.rs index 2dfe8789..8db2df54 100644 --- a/crates/tests/cgp-tests/tests/getters/slice.rs +++ b/crates/tests/cgp-tests/tests/getters/slice.rs @@ -3,7 +3,8 @@ //! byte buffer is exposed as a slice. The context binds the source field via //! `UseField`. //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_getter; diff --git a/crates/tests/cgp-tests/tests/getters/slice_auto.rs b/crates/tests/cgp-tests/tests/getters/slice_auto.rs index 955ec7dc..3798656f 100644 --- a/crates/tests/cgp-tests/tests/getters/slice_auto.rs +++ b/crates/tests/cgp-tests/tests/getters/slice_auto.rs @@ -2,7 +2,7 @@ //! type is `AsRef<[u8]> + 'static` (e.g. `Vec`) named after the method and //! calls `.as_ref()`, exposing an owned byte buffer as a slice without wiring. //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_auto_getter; diff --git a/crates/tests/cgp-tests/tests/getters/string.rs b/crates/tests/cgp-tests/tests/getters/string.rs index 20eaaad6..a606a667 100644 --- a/crates/tests/cgp-tests/tests/getters/string.rs +++ b/crates/tests/cgp-tests/tests/getters/string.rs @@ -2,7 +2,8 @@ //! generated provider impls call `.as_str()`, so a `String` field can be exposed //! as `&str`. The context binds the source field by wiring to `UseField`. //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_getter; diff --git a/crates/tests/cgp-tests/tests/getters/string_auto.rs b/crates/tests/cgp-tests/tests/getters/string_auto.rs index d049c700..60cdb93c 100644 --- a/crates/tests/cgp-tests/tests/getters/string_auto.rs +++ b/crates/tests/cgp-tests/tests/getters/string_auto.rs @@ -2,7 +2,7 @@ //! field named after the method and calls `.as_str()`, exposing a `String` field //! as `&str` without any wiring. //! -//! See docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_auto_getter; diff --git a/crates/tests/cgp-tests/tests/getters/string_custom_name.rs b/crates/tests/cgp-tests/tests/getters/string_custom_name.rs index 7793198a..80bed037 100644 --- a/crates/tests/cgp-tests/tests/getters/string_custom_name.rs +++ b/crates/tests/cgp-tests/tests/getters/string_custom_name.rs @@ -2,7 +2,8 @@ //! `GetString` (and the component to `GetStringComponent`), while the consumer //! trait `HasFoo` and its `&str` return shorthand are unchanged. //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_getter; diff --git a/crates/tests/cgp-tests/tests/getters/string_custom_spec.rs b/crates/tests/cgp-tests/tests/getters/string_custom_spec.rs index af5bb203..c681335c 100644 --- a/crates/tests/cgp-tests/tests/getters/string_custom_spec.rs +++ b/crates/tests/cgp-tests/tests/getters/string_custom_spec.rs @@ -2,7 +2,8 @@ //! the provider name and the component marker name independently, via the brace //! spec form. The consumer trait and its `&str` return shorthand are unchanged. //! -//! See docs/reference/macros/cgp_getter.md and docs/reference/providers/use_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/providers/use_field.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_getter; diff --git a/crates/tests/cgp-tests/tests/getters_tests.rs b/crates/tests/cgp-tests/tests/getters_tests.rs index a9de3a7a..841755ad 100644 --- a/crates/tests/cgp-tests/tests/getters_tests.rs +++ b/crates/tests/cgp-tests/tests/getters_tests.rs @@ -8,8 +8,8 @@ //! `MRef`, clone-on-`Copy`), associated getter types, generic getters, non-`self` //! getters, and abstract-type integration. //! -//! See docs/reference/macros/cgp_getter.md and -//! docs/reference/macros/cgp_auto_getter.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_getter.md and +//! cgp-knowledge-base/cgp/reference/macros/cgp_auto_getter.md. #![allow(dead_code)] #![allow(clippy::disallowed_names)] diff --git a/crates/tests/cgp-tests/tests/handlers/computer_macro.rs b/crates/tests/cgp-tests/tests/handlers/computer_macro.rs index 92d33527..8f445a9a 100644 --- a/crates/tests/cgp-tests/tests/handlers/computer_macro.rs +++ b/crates/tests/cgp-tests/tests/handlers/computer_macro.rs @@ -14,7 +14,7 @@ //! `Handler` has an error type; it uses the plain `delegate_components!` (the //! error and wiring macros are owned by other concept targets). //! -//! See docs/reference/components/computer.md. +//! See cgp-knowledge-base/cgp/reference/components/computer.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/handlers/handler_macro.rs b/crates/tests/cgp-tests/tests/handlers/handler_macro.rs index b6156860..e2e24a4a 100644 --- a/crates/tests/cgp-tests/tests/handlers/handler_macro.rs +++ b/crates/tests/cgp-tests/tests/handlers/handler_macro.rs @@ -10,8 +10,8 @@ //! The error wiring on `App` is incidental scaffolding, so it uses the plain //! `delegate_components!`. //! -//! See docs/reference/components/handler.md and -//! docs/reference/components/computer.md. +//! See cgp-knowledge-base/cgp/reference/components/handler.md and +//! cgp-knowledge-base/cgp/reference/components/computer.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/handlers/pipe_computers.rs b/crates/tests/cgp-tests/tests/handlers/pipe_computers.rs index b25903a0..c925e6cb 100644 --- a/crates/tests/cgp-tests/tests/handlers/pipe_computers.rs +++ b/crates/tests/cgp-tests/tests/handlers/pipe_computers.rs @@ -9,8 +9,8 @@ //! `#[cgp_new_provider]`, and the wiring / check use plain `delegate_components!` //! / `check_components!` (those macros are owned by other concept targets). //! -//! See docs/reference/providers/handler_combinators.md and -//! docs/reference/components/computer.md. +//! See cgp-knowledge-base/cgp/reference/providers/handler_combinators.md and +//! cgp-knowledge-base/cgp/reference/components/computer.md. use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/handlers/pipe_handlers.rs b/crates/tests/cgp-tests/tests/handlers/pipe_handlers.rs index fb52443a..96be981b 100644 --- a/crates/tests/cgp-tests/tests/handlers/pipe_handlers.rs +++ b/crates/tests/cgp-tests/tests/handlers/pipe_handlers.rs @@ -12,8 +12,8 @@ //! `#[cgp_new_provider]`, and the wiring / check use plain `delegate_components!` //! / `check_components!` (those macros are owned by other concept targets). //! -//! See docs/reference/providers/handler_combinators.md and -//! docs/reference/components/handler.md. +//! See cgp-knowledge-base/cgp/reference/providers/handler_combinators.md and +//! cgp-knowledge-base/cgp/reference/components/handler.md. use core::convert::Infallible; use core::marker::PhantomData; diff --git a/crates/tests/cgp-tests/tests/handlers/producer_macro.rs b/crates/tests/cgp-tests/tests/handlers/producer_macro.rs index e7cd8a49..729e8bef 100644 --- a/crates/tests/cgp-tests/tests/handlers/producer_macro.rs +++ b/crates/tests/cgp-tests/tests/handlers/producer_macro.rs @@ -10,7 +10,7 @@ //! The error type wiring on `App` is incidental scaffolding, so it uses the //! plain `delegate_components!`. //! -//! See docs/reference/components/producer.md. +//! See cgp-knowledge-base/cgp/reference/components/producer.md. use cgp::core::error::ErrorTypeProviderComponent; use cgp::extra::handler::{ComputerRef, HandlerRef, TryComputerRef}; diff --git a/crates/tests/cgp-tests/tests/handlers_tests.rs b/crates/tests/cgp-tests/tests/handlers_tests.rs index d5e104c0..0d1c1875 100644 --- a/crates/tests/cgp-tests/tests/handlers_tests.rs +++ b/crates/tests/cgp-tests/tests/handlers_tests.rs @@ -10,9 +10,10 @@ //! `check_components!`, or `delegate_components!` snapshots — those live in their //! owning targets — so the scaffolding here uses the plain macros. //! -//! See docs/reference/components/computer.md, -//! docs/reference/components/producer.md, docs/reference/components/handler.md, -//! and docs/reference/providers/handler_combinators.md. +//! See cgp-knowledge-base/cgp/reference/components/computer.md, +//! cgp-knowledge-base/cgp/reference/components/producer.md, +//! cgp-knowledge-base/cgp/reference/components/handler.md, and +//! cgp-knowledge-base/cgp/reference/providers/handler_combinators.md. #![allow(dead_code)] pub mod handlers; diff --git a/crates/tests/cgp-tests/tests/higher_order_providers/rectangle_or_circle.rs b/crates/tests/cgp-tests/tests/higher_order_providers/rectangle_or_circle.rs index 6a335135..9630fc08 100644 --- a/crates/tests/cgp-tests/tests/higher_order_providers/rectangle_or_circle.rs +++ b/crates/tests/cgp-tests/tests/higher_order_providers/rectangle_or_circle.rs @@ -6,7 +6,8 @@ //! `rectangle_area`/`circle_area` `#[cgp_fn]`s — apply to it. The `#[cgp_fn]` //! expansions are owned by `implicit_arguments`, so they are written plainly here. //! -//! See docs/concepts/higher-order-providers.md and docs/reference/macros/cgp_fn.md. +//! See cgp-knowledge-base/cgp/concepts/higher-order-providers.md and +//! cgp-knowledge-base/cgp/reference/macros/cgp_fn.md. use core::f64::consts::PI; diff --git a/crates/tests/cgp-tests/tests/higher_order_providers/scaled_area.rs b/crates/tests/cgp-tests/tests/higher_order_providers/scaled_area.rs index de82437d..509ff572 100644 --- a/crates/tests/cgp-tests/tests/higher_order_providers/scaled_area.rs +++ b/crates/tests/cgp-tests/tests/higher_order_providers/scaled_area.rs @@ -7,7 +7,7 @@ //! owned by `basic_delegation` and `checking`); the runtime test confirms the //! composed results. //! -//! See docs/concepts/higher-order-providers.md. +//! See cgp-knowledge-base/cgp/concepts/higher-order-providers.md. use core::f64::consts::PI; diff --git a/crates/tests/cgp-tests/tests/higher_order_providers/use_provider_fn.rs b/crates/tests/cgp-tests/tests/higher_order_providers/use_provider_fn.rs index eddb0aa2..f1c276ed 100644 --- a/crates/tests/cgp-tests/tests/higher_order_providers/use_provider_fn.rs +++ b/crates/tests/cgp-tests/tests/higher_order_providers/use_provider_fn.rs @@ -7,7 +7,8 @@ //! component and the inner provider are written plainly (their expansions are //! owned elsewhere); only the `#[use_provider]` `#[cgp_fn]` is snapshotted here. //! -//! See docs/reference/attributes/use_provider.md and docs/reference/macros/cgp_fn.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_provider.md and +//! cgp-knowledge-base/cgp/reference/macros/cgp_fn.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs b/crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs index 44523ac7..664f7759 100644 --- a/crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs +++ b/crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs @@ -11,8 +11,8 @@ //! impl. The plain `RectangleArea` below is incidental scaffolding — its //! `#[implicit]` expansion is snapshotted in `implicit_arguments`. //! -//! See docs/reference/attributes/use_provider.md and -//! docs/concepts/higher-order-providers.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_provider.md and +//! cgp-knowledge-base/cgp/concepts/higher-order-providers.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_impl; diff --git a/crates/tests/cgp-tests/tests/higher_order_providers_tests.rs b/crates/tests/cgp-tests/tests/higher_order_providers_tests.rs index c0a2f711..0fce41a2 100644 --- a/crates/tests/cgp-tests/tests/higher_order_providers_tests.rs +++ b/crates/tests/cgp-tests/tests/higher_order_providers_tests.rs @@ -6,8 +6,8 @@ //! (on both `#[cgp_fn]` and `#[cgp_impl]`) and exercises the scaling pattern where //! an outer calculator wraps an inner one. //! -//! See docs/reference/attributes/use_provider.md and -//! docs/concepts/higher-order-providers.md. +//! See cgp-knowledge-base/cgp/reference/attributes/use_provider.md and +//! cgp-knowledge-base/cgp/concepts/higher-order-providers.md. #![allow(dead_code)] pub mod higher_order_providers; diff --git a/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_extend.rs b/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_extend.rs index d528f573..e8312ead 100644 --- a/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_extend.rs +++ b/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_extend.rs @@ -8,8 +8,9 @@ //! The `#[cgp_type]` scaffolding is written plainly here — its expansion is owned //! by the `abstract_types` concept. //! -//! See docs/concepts/impl-side-dependencies.md, -//! docs/reference/attributes/extend.md, and docs/reference/macros/cgp_fn.md. +//! See cgp-knowledge-base/cgp/concepts/impl-side-dependencies.md, +//! cgp-knowledge-base/cgp/reference/attributes/extend.md, and +//! cgp-knowledge-base/cgp/reference/macros/cgp_fn.md. use std::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs b/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs index d78cc63e..6c78615c 100644 --- a/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs +++ b/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs @@ -9,8 +9,9 @@ //! expansion is owned by the `implicit_arguments` concept), so it is written //! without a snapshot; the snapshot here pins how `#[uses]` lands. //! -//! See docs/concepts/impl-side-dependencies.md, -//! docs/reference/attributes/uses.md, and docs/reference/macros/cgp_fn.md. +//! See cgp-knowledge-base/cgp/concepts/impl-side-dependencies.md, +//! cgp-knowledge-base/cgp/reference/attributes/uses.md, and +//! cgp-knowledge-base/cgp/reference/macros/cgp_fn.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses_associated_type.rs b/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses_associated_type.rs index 86b9943d..c2c8d5ec 100644 --- a/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses_associated_type.rs +++ b/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses_associated_type.rs @@ -12,8 +12,8 @@ //! satisfies the imported bound and gains `AlwaysTrue` — a wrong pin (a different //! `Error` type) would make `impl CheckApp for App` fail to compile. //! -//! See docs/implementation/asts/attributes.md and -//! docs/reference/attributes/uses.md. +//! See cgp-knowledge-base/cgp/implementation/asts/attributes/README.md and +//! cgp-knowledge-base/cgp/reference/attributes/uses.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses.rs b/crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses.rs index 1f042ce9..04b44ee5 100644 --- a/crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses.rs +++ b/crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses.rs @@ -12,8 +12,9 @@ //! `implicit_arguments`, `basic_delegation`, and `checking` concepts), so both are //! written as plain macros. //! -//! See docs/concepts/impl-side-dependencies.md, -//! docs/reference/attributes/uses.md, and docs/reference/macros/cgp_impl.md. +//! See cgp-knowledge-base/cgp/concepts/impl-side-dependencies.md, +//! cgp-knowledge-base/cgp/reference/attributes/uses.md, and +//! cgp-knowledge-base/cgp/reference/macros/cgp_impl.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses_associated_type.rs b/crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses_associated_type.rs index c5185eef..20e80692 100644 --- a/crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses_associated_type.rs +++ b/crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses_associated_type.rs @@ -15,8 +15,8 @@ //! is the preferred spelling; this test exercises the more general bound `#[uses]` //! accepts. //! -//! See docs/implementation/asts/attributes.md and -//! docs/reference/attributes/uses.md. +//! See cgp-knowledge-base/cgp/implementation/asts/attributes/README.md and +//! cgp-knowledge-base/cgp/reference/attributes/uses.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/impl_side_dependencies_tests.rs b/crates/tests/cgp-tests/tests/impl_side_dependencies_tests.rs index 15312764..2e77a1cc 100644 --- a/crates/tests/cgp-tests/tests/impl_side_dependencies_tests.rs +++ b/crates/tests/cgp-tests/tests/impl_side_dependencies_tests.rs @@ -8,9 +8,10 @@ //! snapshots that show how `#[uses]`/`#[extend]` land on the generated trait and //! impl. //! -//! See docs/concepts/impl-side-dependencies.md, -//! docs/reference/attributes/uses.md, docs/reference/attributes/extend.md, and -//! docs/reference/attributes/extend_where.md. +//! See cgp-knowledge-base/cgp/concepts/impl-side-dependencies.md, +//! cgp-knowledge-base/cgp/reference/attributes/uses.md, +//! cgp-knowledge-base/cgp/reference/attributes/extend.md, and +//! cgp-knowledge-base/cgp/reference/attributes/extend_where.md. #![allow(dead_code)] pub mod impl_side_dependencies; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_calling_fn.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_calling_fn.rs index b7af8dfd..9d19146d 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_calling_fn.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_calling_fn.rs @@ -4,7 +4,7 @@ //! stated as an explicit `where` clause) and calls `self.rectangle_area()`. The //! snapshots show both generated traits and how the dependency lands on the impl. //! -//! See docs/reference/macros/cgp_fn.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_custom_trait_name.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_custom_trait_name.rs index 9ad805dd..0ec00961 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_custom_trait_name.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_custom_trait_name.rs @@ -1,7 +1,7 @@ //! `#[cgp_fn(CustomName)]` overrides the generated trait name, with two //! `#[implicit]` `f64` arguments read from the context (each `.clone()`d). //! -//! See docs/reference/macros/cgp_fn.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_greet.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_greet.rs index 53b15a68..7d5118da 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_greet.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_greet.rs @@ -5,7 +5,8 @@ //! `CheckPerson` bound proves any context with a `name: String` field implements //! the generated trait. //! -//! See docs/reference/macros/cgp_fn.md and docs/reference/attributes/implicit.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md and +//! cgp-knowledge-base/cgp/reference/attributes/implicit.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs index 718d582f..e7ac567e 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs @@ -6,7 +6,8 @@ //! `#[use_type(HasFooType.{Foo as FooX}, …)]`, showing how implicit reads //! and abstract-type imports combine in one function. //! -//! See docs/reference/macros/cgp_fn.md and docs/reference/attributes/use_type.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md and +//! cgp-knowledge-base/cgp/reference/attributes/use_type.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_self_immutable.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_self_immutable.rs index 623995f1..b59137d1 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_self_immutable.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_self_immutable.rs @@ -8,7 +8,8 @@ //! any number of immutable implicits may share a `&mut self` receiver (only a //! `&mut` implicit is restricted to being the sole implicit argument). //! -//! See docs/reference/macros/cgp_fn.md and docs/reference/attributes/implicit.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md and +//! cgp-knowledge-base/cgp/reference/attributes/implicit.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_slice.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_slice.rs index 7c5d6a7f..7738a452 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_slice.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_slice.rs @@ -7,7 +7,8 @@ //! *argument's* own type, so it is the `&mut` in `&mut [T]` — not the receiver — //! that selects the mutable read. //! -//! See docs/reference/macros/cgp_fn.md and docs/reference/attributes/implicit.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md and +//! cgp-knowledge-base/cgp/reference/attributes/implicit.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mutable.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mutable.rs index ce234787..9e2bc4d9 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mutable.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mutable.rs @@ -3,7 +3,8 @@ //! A `&mut` implicit reads through `HasFieldMut`/`get_field_mut` instead of the //! shared `HasField` path. //! -//! See docs/reference/macros/cgp_fn.md and docs/reference/traits/has_field.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md and +//! cgp-knowledge-base/cgp/reference/traits/has_field.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_option_mut.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_option_mut.rs index 2296801d..56818ed8 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_option_mut.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_option_mut.rs @@ -7,7 +7,8 @@ //! borrows the field with the *inner* reference's mutability, so the `&mut` inside //! the `Option` selects the mutable read and requires the `&mut self` receiver. //! -//! See docs/reference/macros/cgp_fn.md and docs/reference/attributes/implicit.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md and +//! cgp-knowledge-base/cgp/reference/attributes/implicit.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_option_str.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_option_str.rs index 2f1deab7..8b2c36ce 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_option_str.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_option_str.rs @@ -5,7 +5,8 @@ //! `Option<&T>` reference case — so the body receives an `Option<&str>` while the //! context stores an `Option`. //! -//! See docs/reference/macros/cgp_fn.md and docs/reference/attributes/implicit.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md and +//! cgp-knowledge-base/cgp/reference/attributes/implicit.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_owned_tuple.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_owned_tuple.rs index 082f66d8..874e2a08 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_owned_tuple.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_owned_tuple.rs @@ -4,7 +4,8 @@ //! shape — a tuple, an array, or a plain path type all read a `Value = T` field. //! This pins that a tuple field is accepted rather than rejected. //! -//! See docs/reference/macros/cgp_fn.md and docs/reference/attributes/implicit.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md and +//! cgp-knowledge-base/cgp/reference/attributes/implicit.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_slice.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_slice.rs index 91fb1a31..1140d331 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_slice.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_slice.rs @@ -7,7 +7,8 @@ //! `AsRef<[u8]>` bound even though the receiver is `&mut self` — it is not forced //! into a plain-reference `Value = [u8]` bound that no context could satisfy. //! -//! See docs/reference/macros/cgp_fn.md and docs/reference/attributes/implicit.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_fn.md and +//! cgp-knowledge-base/cgp/reference/attributes/implicit.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_fn; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit.rs index d1070da8..e35b5110 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit.rs @@ -8,7 +8,8 @@ //! `IsProviderFor` impl. (The basic, no-implicit `#[cgp_impl]` snapshot lives in //! `basic_delegation`.) //! -//! See docs/reference/macros/cgp_impl.md and docs/reference/attributes/implicit.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_impl.md and +//! cgp-knowledge-base/cgp/reference/attributes/implicit.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_impl; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit_generic.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit_generic.rs index 9cd6dac2..32a05447 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit_generic.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit_generic.rs @@ -4,8 +4,8 @@ //! read as `Scalar` from the context. `#[check_params(f64)]` picks the concrete //! `Scalar` for the wiring check. //! -//! See docs/reference/macros/cgp_impl.md and -//! docs/reference/macros/delegate_and_check_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_impl.md and +//! cgp-knowledge-base/cgp/reference/macros/delegate_and_check_components.md. use core::ops::Mul; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/implicit_context.rs b/crates/tests/cgp-tests/tests/implicit_arguments/implicit_context.rs index 3ea79a55..5f8bd55c 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/implicit_context.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/implicit_context.rs @@ -5,7 +5,7 @@ //! context-free one (`ValueToString`) to ones that depend on the context //! (`WithNamePrefix` needs `HasName`) or carry their own generics (`WithFooTag`). //! -//! See docs/reference/macros/cgp_impl.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_impl.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments_tests.rs b/crates/tests/cgp-tests/tests/implicit_arguments_tests.rs index 9ac83aec..e1d3dc43 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments_tests.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments_tests.rs @@ -6,8 +6,9 @@ //! `HasField` bounds and field reads), and exercises implicit args in `#[cgp_impl]` //! providers, including the implicit context parameter. //! -//! See docs/reference/attributes/implicit.md, docs/reference/macros/cgp_fn.md, -//! and docs/concepts/implicit-arguments.md. +//! See cgp-knowledge-base/cgp/reference/attributes/implicit.md, +//! cgp-knowledge-base/cgp/reference/macros/cgp_fn.md, and +//! cgp-knowledge-base/cgp/concepts/implicit-arguments.md. #![allow(dead_code)] pub mod implicit_arguments; diff --git a/crates/tests/cgp-tests/tests/monadic_handlers/err_monadic.rs b/crates/tests/cgp-tests/tests/monadic_handlers/err_monadic.rs index 4b648d63..d7f7d8f5 100644 --- a/crates/tests/cgp-tests/tests/monadic_handlers/err_monadic.rs +++ b/crates/tests/cgp-tests/tests/monadic_handlers/err_monadic.rs @@ -8,8 +8,8 @@ //! `BindErr` (inside a plain `PipeHandlers` chain) and the //! `PipeMonadic` combinator are exercised. //! -//! See docs/concepts/monadic-handlers.md and -//! docs/reference/providers/monad_providers.md. +//! See cgp-knowledge-base/cgp/concepts/monadic-handlers.md and +//! cgp-knowledge-base/cgp/reference/providers/monad_providers.md. use cgp::extra::handler::PipeHandlers; use cgp::extra::monad::monadic::err::{BindErr, ErrMonadic}; diff --git a/crates/tests/cgp-tests/tests/monadic_handlers/ok_err_monadic_trans.rs b/crates/tests/cgp-tests/tests/monadic_handlers/ok_err_monadic_trans.rs index a1af76fb..65bda261 100644 --- a/crates/tests/cgp-tests/tests/monadic_handlers/ok_err_monadic_trans.rs +++ b/crates/tests/cgp-tests/tests/monadic_handlers/ok_err_monadic_trans.rs @@ -9,8 +9,8 @@ //! `try_compute` (via a plain `OkMonadic` over an `ErrorOnly` context), and the //! async `handle` (blocked on with `futures::executor::block_on`). //! -//! See docs/concepts/monadic-handlers.md and -//! docs/reference/providers/monad_providers.md. +//! See cgp-knowledge-base/cgp/concepts/monadic-handlers.md and +//! cgp-knowledge-base/cgp/reference/providers/monad_providers.md. use cgp::core::error::ErrorOnly; use cgp::extra::monad::monadic::err::ErrMonadic; diff --git a/crates/tests/cgp-tests/tests/monadic_handlers/ok_monadic.rs b/crates/tests/cgp-tests/tests/monadic_handlers/ok_monadic.rs index 0ea23d9b..cda2d7e9 100644 --- a/crates/tests/cgp-tests/tests/monadic_handlers/ok_monadic.rs +++ b/crates/tests/cgp-tests/tests/monadic_handlers/ok_monadic.rs @@ -9,8 +9,8 @@ //! `PipeHandlers` chain) and the `PipeMonadic` combinator are //! exercised. //! -//! See docs/concepts/monadic-handlers.md and -//! docs/reference/providers/monad_providers.md. +//! See cgp-knowledge-base/cgp/concepts/monadic-handlers.md and +//! cgp-knowledge-base/cgp/reference/providers/monad_providers.md. use cgp::extra::handler::PipeHandlers; use cgp::extra::monad::monadic::ident::IdentMonadic; diff --git a/crates/tests/cgp-tests/tests/monadic_handlers_tests.rs b/crates/tests/cgp-tests/tests/monadic_handlers_tests.rs index 7ca92f44..f1e72351 100644 --- a/crates/tests/cgp-tests/tests/monadic_handlers_tests.rs +++ b/crates/tests/cgp-tests/tests/monadic_handlers_tests.rs @@ -6,8 +6,8 @@ //! passing test is an actual assertion on the computed value, not just //! successful compilation. //! -//! See docs/concepts/monadic-handlers.md and -//! docs/reference/providers/monad_providers.md. +//! See cgp-knowledge-base/cgp/concepts/monadic-handlers.md and +//! cgp-knowledge-base/cgp/reference/providers/monad_providers.md. #![allow(dead_code)] pub mod monadic_handlers; diff --git a/crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs b/crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs index 97dd7fbb..a124370c 100644 --- a/crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs +++ b/crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs @@ -12,8 +12,8 @@ //! `__Components__` and no `where` clause; the wiring below proves a context //! resolves the provider (and its abstract-type dependency) through the namespace. //! -//! See docs/implementation/asts/attributes.md (`#[default_impl]`) and -//! docs/reference/traits/default_namespace.md. +//! See cgp-knowledge-base/cgp/implementation/asts/attributes/README.md (`#[default_impl]`) and +//! cgp-knowledge-base/cgp/reference/traits/default_namespace.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_cgp_impl; diff --git a/crates/tests/cgp-tests/tests/namespaces/default_impls.rs b/crates/tests/cgp-tests/tests/namespaces/default_impls.rs index 0970db21..3629bc4c 100644 --- a/crates/tests/cgp-tests/tests/namespaces/default_impls.rs +++ b/crates/tests/cgp-tests/tests/namespaces/default_impls.rs @@ -8,8 +8,9 @@ //! snapshots capture the namespace/default-impl wiring this concept owns; sibling //! `default_impls_wiring` consumes the exported items. //! -//! See docs/reference/macros/cgp_namespace.md, -//! docs/reference/traits/default_namespace.md, and docs/concepts/namespaces.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_namespace.md, +//! cgp-knowledge-base/cgp/reference/traits/default_namespace.md, and +//! cgp-knowledge-base/cgp/concepts/namespaces.md. use core::fmt::Display; diff --git a/crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs b/crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs index 069d59d3..9319f7d7 100644 --- a/crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs +++ b/crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs @@ -9,8 +9,8 @@ //! snapshots) against the reusable namespaces defined in the sibling //! `default_impls` module. //! -//! See docs/reference/traits/default_namespace.md and -//! docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/traits/default_namespace.md and +//! cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use cgp::core::component::DefaultImpls1; use cgp::prelude::*; diff --git a/crates/tests/cgp-tests/tests/namespaces/extended.rs b/crates/tests/cgp-tests/tests/namespaces/extended.rs index f81a587d..67819f83 100644 --- a/crates/tests/cgp-tests/tests/namespaces/extended.rs +++ b/crates/tests/cgp-tests/tests/namespaces/extended.rs @@ -7,7 +7,8 @@ //! the namespace-inheritance wiring this concept owns; sibling //! `extended_namespace_wiring` consumes the exported `ExtendedNamespace`. //! -//! See docs/reference/macros/cgp_namespace.md and docs/concepts/namespaces.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_namespace.md and +//! cgp-knowledge-base/cgp/concepts/namespaces.md. use cgp::prelude::DefaultNamespace; use cgp_macro_test_util::snapshot_cgp_namespace; diff --git a/crates/tests/cgp-tests/tests/namespaces/extended_namespace_wiring.rs b/crates/tests/cgp-tests/tests/namespaces/extended_namespace_wiring.rs index f32fd1dd..3240b585 100644 --- a/crates/tests/cgp-tests/tests/namespaces/extended_namespace_wiring.rs +++ b/crates/tests/cgp-tests/tests/namespaces/extended_namespace_wiring.rs @@ -8,8 +8,8 @@ //! `delegate_components!` (kept as a snapshot) and confirms the wiring compiles //! via the `CheckApp` supertrait bundle. //! -//! See docs/reference/macros/cgp_namespace.md and -//! docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_namespace.md and +//! cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use cgp::core::error::{ErrorRaiserComponent, ErrorTypeProviderComponent, ErrorWrapperComponent}; use cgp::extra::error::RaiseFrom; diff --git a/crates/tests/cgp-tests/tests/namespaces/for_where_clause.rs b/crates/tests/cgp-tests/tests/namespaces/for_where_clause.rs index 4428ce3d..4fa7b785 100644 --- a/crates/tests/cgp-tests/tests/namespaces/for_where_clause.rs +++ b/crates/tests/cgp-tests/tests/namespaces/for_where_clause.rs @@ -7,8 +7,8 @@ //! snapshotted — the owning targets pin their expansions); the //! `delegate_components!` `for`-loop expansion is what this file captures. //! -//! See docs/implementation/entrypoints/cgp_namespace.md (Tests) and -//! docs/reference/macros/cgp_namespace.md. +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_namespace.md (Tests) and +//! cgp-knowledge-base/cgp/reference/macros/cgp_namespace.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_delegate_components; diff --git a/crates/tests/cgp-tests/tests/namespaces/multi_param_namespace.rs b/crates/tests/cgp-tests/tests/namespaces/multi_param_namespace.rs index c4c73b73..4be10b51 100644 --- a/crates/tests/cgp-tests/tests/namespaces/multi_param_namespace.rs +++ b/crates/tests/cgp-tests/tests/namespaces/multi_param_namespace.rs @@ -8,8 +8,8 @@ //! namespace `delegate_components!` snapshot is the canonical golden output; the //! component and provider are incidental scaffolding. //! -//! See docs/reference/traits/default_namespace.md and -//! docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/traits/default_namespace.md and +//! cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_delegate_components; diff --git a/crates/tests/cgp-tests/tests/namespaces/multi_param_open.rs b/crates/tests/cgp-tests/tests/namespaces/multi_param_open.rs index 43566d3b..6b0f97ae 100644 --- a/crates/tests/cgp-tests/tests/namespaces/multi_param_open.rs +++ b/crates/tests/cgp-tests/tests/namespaces/multi_param_open.rs @@ -7,7 +7,7 @@ //! not the delegate path). The `delegate_components!` snapshot is the canonical //! `open` golden output; the component and provider are incidental scaffolding. //! -//! See docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_delegate_components; diff --git a/crates/tests/cgp-tests/tests/namespaces/namespace_basic.rs b/crates/tests/cgp-tests/tests/namespaces/namespace_basic.rs index 9d0711fa..b7b13207 100644 --- a/crates/tests/cgp-tests/tests/namespaces/namespace_basic.rs +++ b/crates/tests/cgp-tests/tests/namespaces/namespace_basic.rs @@ -9,8 +9,8 @@ //! component and the two provider impls are incidental scaffolding, written with //! the plain macros (their expansion is pinned in `basic_delegation`). //! -//! See docs/reference/macros/cgp_namespace.md and -//! docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_namespace.md and +//! cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use cgp::prelude::*; use cgp_macro_test_util::{ diff --git a/crates/tests/cgp-tests/tests/namespaces/namespace_group.rs b/crates/tests/cgp-tests/tests/namespaces/namespace_group.rs index 8de8916b..1025b50c 100644 --- a/crates/tests/cgp-tests/tests/namespaces/namespace_group.rs +++ b/crates/tests/cgp-tests/tests/namespaces/namespace_group.rs @@ -9,8 +9,8 @@ //! output this concept owns; the components and `DummyImpl` are incidental //! scaffolding written plainly. //! -//! See docs/reference/macros/delegate_components.md and -//! docs/reference/traits/default_namespace.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_components.md and +//! cgp-knowledge-base/cgp/reference/traits/default_namespace.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_delegate_components; diff --git a/crates/tests/cgp-tests/tests/namespaces/namespace_multi.rs b/crates/tests/cgp-tests/tests/namespaces/namespace_multi.rs index e506374d..aafc5227 100644 --- a/crates/tests/cgp-tests/tests/namespaces/namespace_multi.rs +++ b/crates/tests/cgp-tests/tests/namespaces/namespace_multi.rs @@ -9,8 +9,8 @@ //! `delegate_components!` snapshots are kept; the plain `Foo` component and the //! provider impls are incidental scaffolding. //! -//! See docs/reference/macros/cgp_namespace.md and -//! docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_namespace.md and +//! cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use cgp::prelude::*; use cgp_macro_test_util::{ diff --git a/crates/tests/cgp-tests/tests/namespaces/namespace_symbol_path.rs b/crates/tests/cgp-tests/tests/namespaces/namespace_symbol_path.rs index f4028885..69e2ba6a 100644 --- a/crates/tests/cgp-tests/tests/namespaces/namespace_symbol_path.rs +++ b/crates/tests/cgp-tests/tests/namespaces/namespace_symbol_path.rs @@ -7,8 +7,8 @@ //! snapshots pin that encoding; the plain `Foo` component and the provider impls //! are incidental scaffolding written with the plain macros. //! -//! See docs/reference/macros/cgp_namespace.md and -//! docs/reference/providers/redirect_lookup.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_namespace.md and +//! cgp-knowledge-base/cgp/reference/providers/redirect_lookup.md. use cgp::prelude::*; use cgp_macro_test_util::{ diff --git a/crates/tests/cgp-tests/tests/namespaces/namespace_type_path.rs b/crates/tests/cgp-tests/tests/namespaces/namespace_type_path.rs index 6bfa198b..5d94432f 100644 --- a/crates/tests/cgp-tests/tests/namespaces/namespace_type_path.rs +++ b/crates/tests/cgp-tests/tests/namespaces/namespace_type_path.rs @@ -7,8 +7,8 @@ //! and namespace `delegate_components!` snapshots pin that encoding; the plain //! `Foo` component and the provider impls are incidental scaffolding. //! -//! See docs/reference/macros/cgp_namespace.md and -//! docs/reference/providers/redirect_lookup.md. +//! See cgp-knowledge-base/cgp/reference/macros/cgp_namespace.md and +//! cgp-knowledge-base/cgp/reference/providers/redirect_lookup.md. use cgp::prelude::*; use cgp_macro_test_util::{ diff --git a/crates/tests/cgp-tests/tests/namespaces/open_dispatch.rs b/crates/tests/cgp-tests/tests/namespaces/open_dispatch.rs index c01fe24a..650bc6c1 100644 --- a/crates/tests/cgp-tests/tests/namespaces/open_dispatch.rs +++ b/crates/tests/cgp-tests/tests/namespaces/open_dispatch.rs @@ -10,8 +10,8 @@ //! snapshot is the canonical `open` golden output this concept owns; the two //! components and their providers are incidental scaffolding written plainly. //! -//! See docs/reference/macros/delegate_components.md and -//! docs/reference/providers/redirect_lookup.md. +//! See cgp-knowledge-base/cgp/reference/macros/delegate_components.md and +//! cgp-knowledge-base/cgp/reference/providers/redirect_lookup.md. use cgp::prelude::*; use cgp_macro_test_util::snapshot_delegate_components; diff --git a/crates/tests/cgp-tests/tests/namespaces/prefix_default_namespace.rs b/crates/tests/cgp-tests/tests/namespaces/prefix_default_namespace.rs index 2c27b6c6..046ab3b4 100644 --- a/crates/tests/cgp-tests/tests/namespaces/prefix_default_namespace.rs +++ b/crates/tests/cgp-tests/tests/namespaces/prefix_default_namespace.rs @@ -9,8 +9,8 @@ //! The `#[prefix]`-component and namespace `delegate_components!` snapshots are the //! canonical golden output this concept owns. //! -//! See docs/reference/traits/default_namespace.md and -//! docs/reference/macros/delegate_components.md. +//! See cgp-knowledge-base/cgp/reference/traits/default_namespace.md and +//! cgp-knowledge-base/cgp/reference/macros/delegate_components.md. use cgp::core::error::{ErrorRaiserComponent, ErrorTypeProviderComponent}; use cgp::extra::error::ReturnError; diff --git a/crates/tests/cgp-tests/tests/namespaces/redirect_lookup.rs b/crates/tests/cgp-tests/tests/namespaces/redirect_lookup.rs index 3884100a..05d3a2f1 100644 --- a/crates/tests/cgp-tests/tests/namespaces/redirect_lookup.rs +++ b/crates/tests/cgp-tests/tests/namespaces/redirect_lookup.rs @@ -8,8 +8,8 @@ //! `#[prefix]`-component and namespace `delegate_components!` snapshots are the //! canonical golden output; the `TestProvider` impl is incidental scaffolding. //! -//! See docs/reference/providers/redirect_lookup.md and -//! docs/reference/traits/default_namespace.md. +//! See cgp-knowledge-base/cgp/reference/providers/redirect_lookup.md and +//! cgp-knowledge-base/cgp/reference/traits/default_namespace.md. use cgp::prelude::*; use cgp_macro_test_util::{snapshot_cgp_component, snapshot_delegate_components}; diff --git a/crates/tests/cgp-tests/tests/namespaces_tests.rs b/crates/tests/cgp-tests/tests/namespaces_tests.rs index 0c3655ff..1529b759 100644 --- a/crates/tests/cgp-tests/tests/namespaces_tests.rs +++ b/crates/tests/cgp-tests/tests/namespaces_tests.rs @@ -13,10 +13,11 @@ //! `delegate_components!`; incidental uses of other macros are written plainly, //! since their expansion is pinned in their owning target. //! -//! See docs/concepts/namespaces.md, docs/reference/macros/cgp_namespace.md, -//! docs/reference/macros/delegate_components.md, -//! docs/reference/providers/redirect_lookup.md, and -//! docs/reference/traits/default_namespace.md. +//! See cgp-knowledge-base/cgp/concepts/namespaces.md, +//! cgp-knowledge-base/cgp/reference/macros/cgp_namespace.md, +//! cgp-knowledge-base/cgp/reference/macros/delegate_components.md, +//! cgp-knowledge-base/cgp/reference/providers/redirect_lookup.md, and +//! cgp-knowledge-base/cgp/reference/traits/default_namespace.md. #![allow(dead_code)] pub mod namespaces; diff --git a/docs/AGENTS.md b/docs/AGENTS.md deleted file mode 100644 index ce42a25d..00000000 --- a/docs/AGENTS.md +++ /dev/null @@ -1,109 +0,0 @@ -# AGENTS.md - -This file provides guidance to LLM agents when working with code in this repository. - -This directory is the CGP knowledge base — agent-maintained documentation whose job is to record the full semantics of every CGP construct. Read [README.md](README.md) for the background and motivation behind it. The rules below govern how to keep it correct. - -## The synchronization rule - -**Documentation here must stay in sync with the code, and keeping it in sync is part of the change, not a follow-up.** Whenever you modify a CGP construct — its accepted syntax, the code it expands to, its defaults, its error behavior, or its relationships to other constructs — you must update the matching reference document in the same change. A reference document that describes behavior the code no longer has is actively harmful: the next agent will trust it and be misled. Treat a stale document as a bug in the change that made it stale. - -This rule applies in both directions. If you add a new construct, add its reference document and register it in the [reference index](reference/README.md). If you remove a construct, remove or supersede its document and update the index. If you change a construct's expansion, revise the "Expansion" section of its document so the desugaring shown still matches what the macro emits. - -The implementation in [crates/macros/cgp-macro-core](../crates/macros/cgp-macro-core), the expansion snapshots in [crates/tests/cgp-macro-tests](../crates/tests/cgp-macro-tests), the reference documents here, the implementation documents under [implementation/](implementation/README.md), and the agent skill under [skills/](skills/README.md) are views of the same truth. When they disagree, that disagreement is a defect. The snapshots are the most mechanical check on whether a document's "Expansion" section is honest — when you doubt what a macro emits, generate or read the snapshot rather than guessing. The skill is the most distilled view, so a change to a construct's behavior must propagate all the way out to it; see [the skills directory](#the-skills-directory) for how to keep it in sync. - -## Authoring conventions - -Invoke the `/cgp` skill before writing or revising any reference document. The skill is the authoritative source for CGP semantics and terminology; the reference documents must use the same vocabulary (consumer trait, provider trait, provider, wiring, impl-side dependency, and so on) so that a reader moving between the skill and the documents never has to reconcile two dialects. - -Write every reference document in the dual-reader prose style (the `/dual-reader-prose` skill). Each document is read both by agents scanning for one specific fact and by agents reading start-to-finish for complete understanding, so every section opens with a self-contained topic sentence that states its point, followed by elaboration. Avoid orphaned bullet lists; frame any list with a sentence before and after. Use code blocks freely — showing the exact expansion is the whole point — but the prose around them must carry the meaning on its own. - -Prefer code snippets that match the worked examples in [examples/](examples/). When a reference document's Examples section (or any illustrative snippet) needs running code, draw on the contexts, components, and providers used in an example document rather than inventing a fresh scenario, so the same vocabulary and the same running use cases recur across the whole knowledge base. When a review turns up a snippet built on an ad-hoc scenario that an example already covers better, replace it with the example-aligned version as part of that review. The examples directory is described in [examples/README.md](examples/README.md), and the rules for maintaining it are below. - -Verify against the source before writing, not from memory. Read the construct's implementation in `cgp-macro-core` (its `types//` module and the `cgp-macro-lib` entry that drives it) and any tests that exercise it. The "Expansion" section is a claim about generated code; it must reflect what the macro actually produces today, including the real default identifiers (for example, `#[cgp_component]` defaults the context type to `__Context__` and the component name to `{Provider}Component`), not idealized names used for teaching. - -Document the present, not the history. A reference document describes what a construct does now, written as though it had always behaved that way. Do not record how the code reached its current state: no changelog entries, no version numbers attached to behavior, and no "renamed from", "previously", "used to", "no longer", or notes about a past mismatch between the code and the skill or an earlier draft of the document. These historical artifacts accumulate into noise that ages badly — a reader cannot tell which "recently" is which, and a comparison against a former state describes something that no longer exists. When you correct a discrepancy, rewrite the prose to state the current behavior and delete the old wording entirely, rather than leaving a trace of what it used to say. The one place a document describes a deviation is the Known issues section, and even there the deviation is a current one, not a past one. - -## The examples directory - -The [examples/](examples/) directory holds self-contained worked examples — one document per use case, each developing a realistic scenario from its contexts and components through to the wiring that connects them. Examples exist for two reasons: they are the canonical source of code snippets the reference documents reuse (see the note in Authoring conventions above), and they are the raw material an agent draws on when writing expanded documentation such as a tutorial or guide. An example is judged by whether it is quotable and correct, not by whether it covers every detail. - -An example is not a second copy of the reference. A reference document explains one construct completely — its syntax, exact expansion, and corner cases — while an example shows several constructs cooperating to solve a problem and deliberately leaves the mechanics to the reference. Keep the prose in an example light: enough to make the code legible, a short note on which CGP concept each step demonstrates, and a link to the reference document that owns that concept. Do not re-explain a construct an example uses; link to it instead. Examples are still subject to the synchronization rule — code that no longer reflects current CGP is a bug — so verify every snippet against the source the same way you would a reference document's Expansion section. - -To add a new example from a source — example code, an article, a tutorial, or any external write-up — treat the source as a *reference for the scenario and the patterns*, then write a fresh, self-contained example document that stands on its own. Do not cite, name, link, or otherwise point back to the original source; the example must read as native knowledge-base material with no dependency on where the idea came from. Re-derive the code in current CGP vocabulary and verify it against the implementation rather than copying the source's code verbatim, since the source may use older syntax or a different dialect. Give the example its own coherent narrative arc — usually a progression from the simplest form of the use case to the fully wired and composed version — rather than mirroring the source's structure. - -Follow the shape of the existing examples. Open with a level-one heading naming the use case and a one-sentence summary of what it demonstrates and why it is a useful template. Follow that with a short framed list of the concepts the example demonstrates, each linking to its reference document, and a note of any shared assumption such as `use cgp::prelude::*;`. Then develop the use case in titled sections, each introducing one step of the progression with a sentence of context, the code, and a brief concept note pointing into the reference. Register the new document in the catalog in [examples/README.md](examples/README.md) in the same change. - -When an example would demonstrate a concept the reference does not yet cover, document the concept where it belongs rather than explaining it inside the example. Add the missing detail to the relevant reference document, or — when the concept is a cross-cutting idea that ties several constructs together — add a new page under [concepts/](concepts/) and register it in the [concepts index](concepts/README.md). The example then links to that documentation like any other, keeping the example itself focused on the use case rather than on teaching a new construct. - -## The guides directory - -The [guides/](guides/) directory holds prescriptive guides to *writing* CGP — documents that direct the choices an author makes when several constructs could express the same thing. A guide is not a reference and not a concept: where a reference states what a construct means and a concept explains an idea, a guide recommends a default form, names the trade-offs of the alternatives, and usually walks a concrete before/after refactoring to ground the recommendation in real code. Choosing a construct's vanilla-looking form over its explicit equivalent, shrinking wiring tables with namespaces, and debugging compile errors are the current guides; [guides/README.md](guides/README.md) is the catalog you register a new one in. - -A guide leans on the other sections rather than restating them. Link to the reference for the exact syntax of every construct a guide recommends, to the concepts for the mechanism behind a recommendation, and to the examples for a fuller worked scenario; keep the guide itself focused on the decision and the migration path. When a guide starts explaining what a construct *is* at length, move that explanation into the reference or a concept and link to it. Guides are bound by the synchronization rule like everything else: a recommendation that names a syntax, expansion, or default the code no longer has is a bug in the change that made it stale, and every code snippet a guide shows must compile against current CGP — verify it against the source the same way you would a reference document's Expansion section. Write guides in the dual-reader prose style, and prefer the vocabulary and running scenarios the rest of the knowledge base already uses. - -## The errors directory - -The [errors/](errors/README.md) directory catalogs the compiler errors CGP produces *after* codegen — input a macro accepts and lowers to Rust that then fails to compile — organized by the kind of error rather than by the macro. It is the counterpart to the implementation tree's per-macro failure documentation, gathered into a reader-facing catalog and built around whether the compiler surfaces or hides each class's root cause. The dividing line with the rest of the knowledge base is ownership: a failure the *compiler* raises while checking a macro's output is documented here and is the canonical home for the post-codegen compile-fail cases, which are pinned as UI fixtures in [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) (migrated out of the former `cgp-compile-fail-tests` suite, now removed) and linked from each class as a GitHub URL; a failure a macro raises by *rejecting* its input stays with the macro's implementation document. Its own [errors/AGENTS.md](errors/AGENTS.md) carries the authoring rules — chiefly that a document summarizes the *kind* of error, records both the raw diagnostic and how `cargo-cgp` reshapes it, and states the *position* of the root cause rather than pasting verbatim output — and [errors/README.md](errors/README.md) is the catalog you register a new error document in. A macro's implementation `## Failure modes` section now links out to the catalog class and its `cargo-cgp` fixture rather than describing a local one; per the cross-project [sync rule](../AGENTS.md#the-two-projects-cgp-and-cargo-cgp), a diagnostic change updates the fixture in `cargo-cgp` and the class here together. - -## The skills directory - -The [skills/](skills/README.md) directory holds the agent skills built from this knowledge base — currently the `cgp` skill, the authoritative orientation an agent loads before reading or writing CGP code. The skill is a distilled, self-contained synthesis of the reference, concepts, and examples: a top-level `SKILL.md` carrying the mental model and a router, plus a `references/` set of sub-skills, one per topic area, each giving enough detail and worked examples to make an agent proficient in that area without further lookup. It is deliberately lighter than the reference documents — it teaches how to read and write CGP, not every corner case — and it is the skill that `/cgp` resolves to, replacing any earlier version. - -The skill is bound by the synchronization rule like everything else here, and because it is the most distilled view it is also the easiest to leave stale. When you change a construct's syntax, expansion, defaults, or recommended form, update the matching sub-skill in the same change: revise the affected sub-skill in `skills/cgp/references/`, the router and reading cheat-sheet in `skills/cgp/SKILL.md` if the change touches a core construct, and the triggering `description` if you add or rename a construct an agent should recognize. The skill must never teach syntax the code no longer has. When a form becomes legacy — as `UseDelegate`/`#[derive_delegate]` did when the `open` statement became the preferred dispatch — the skill leads with the current form and keeps the legacy one only as a clearly-labeled note for *reading* existing code, mirroring how the reference documents treat it. - -Two constraints are unique to the skill because it is deployed on its own, copied out of this repository. First, it must be self-contained: no link may point outside the skill directory — not to `../reference/`, not to any repo path — because those targets will not exist where the skill runs. Cross-links between sub-skills are plain relative filenames (`[wiring](wiring.md)`); for exhaustive detail that the skill deliberately omits, link to the online knowledge base at `https://github.com/contextgeneric/cgp/tree/main/docs` (a file as `…/blob/main/docs/`, a directory as `…/tree/main/docs/`) and ask the agent to fetch it when needed, rather than assuming a local copy. Second, the skill must use the same vocabulary as the reference — consumer trait, provider trait, provider, wiring, impl-side dependency, component — so an agent moving between the skill and the documents never reconciles two dialects. - -Verify the skill against the source the same way you verify a reference document: every snippet must reflect current v0.8.0 syntax, and when you doubt a form, compile a representative snippet against the crates (a scratch test under [crates/tests/cgp-tests](../crates/tests/cgp-tests) is the quickest check) rather than trusting memory or an older draft. The skill ships with an eval set under `skills/cgp/evals/` and a workspace of benchmark runs; when a change alters what good CGP code looks like, re-run or extend the evals so the skill's quality stays measured rather than assumed. - -## The implementation directory - -The [implementation/](implementation/README.md) directory documents the *internals* of the CGP macros — how each macro is built, as opposed to what it does for a user. It is the counterpart to this reference: where a reference document explains a construct's syntax and expansion and points only at library source, an implementation document explains the macro's pipeline, the AST types it parses into, the functions that synthesize each generated item, its corner-case handling, and its known limitations, and it is the sole home for every pointer into the [test suite](../crates/tests). An agent asked to review, debug, or extend the macro source reads the implementation document first. The tree is organized into `entrypoints/` (one document per user-facing macro), `asts/` (the `cgp-macro-core` types that implement `Parse`/`ToTokens`, grouped by evaluation stack), `functions/` (standalone helpers, split into `parse/` and `derive/`), and `macros/` (internal `macro_rules!` such as `parse_internal!`). Its own [implementation/AGENTS.md](implementation/AGENTS.md) carries the authoring rules and document templates for that tree, and [implementation/README.md](implementation/README.md) is the catalog you register a new implementation document in. - -The two trees are bound together and must not drift or duplicate. When you change a macro, update both views in the same change: the reference document's Expansion for the user-facing contract, and the implementation document's Pipeline and Generated items for the mechanics. Every reference document's Source section links to its implementation counterpart, and the implementation document links back to the reference for the user-facing semantics rather than re-deriving them; corner-case behavior a reference document does not want to explain in full is elaborated in the implementation document instead. Because all test and snapshot pointers now live under `implementation/`, moving or renaming a test updates the implementation document's Tests or Snapshots section, never a reference document. - -## Document structure - -Each reference document follows the same shape so readers can navigate any of them by habit. Open with a level-one heading naming the construct and a one-sentence summary of what it is. Then proceed through these sections, using the same headings: - -- **Purpose** — the problem the construct solves and why it exists, in prose. -- **Syntax** — the accepted forms of the construct, with the meaning of each argument and option. -- **Syntax Grammar** — present only for a macro that has custom syntax (a bespoke attribute argument or macro-body grammar that is not just a plain Rust item); a formal grammar of that syntax in the Rust Reference's notation. Omit the heading entirely for a macro whose invocation is a plain Rust item with no arguments. See the grammar conventions below for the notation and what counts as custom syntax. -- **Expansion** — the exact code the construct desugars to, shown with before/after code blocks. This is the heart of the document and the part most likely to drift; keep it faithful to the current macro output. -- **Examples** — at least one realistic, self-contained example showing the construct in use. -- **Related constructs** — links to the reference documents for constructs commonly used with this one, with a phrase explaining each relationship. -- **Known issues** — optional; present only when the construct has corner cases, surprising behavior, or open bugs worth warning a reader about. Omit the heading entirely when there is nothing to record. See the review workflow below for what belongs here. -- **Source** — pointers to the implementing modules in `cgp-macro-core` and `cgp-macro-lib`, and a link to the construct's implementation document under [implementation/](implementation/README.md), so a reader can drop from prose into the code and its internal walkthrough. A reference document never points at a test file; all test pointers live in the implementation document instead. Write this section as a bullet list — one pointer per bullet — rather than a flowing paragraph; a short lead-in sentence is optional, but each source pointer is its own bullet. - -Place each document in the subdirectory that matches what the construct is. The reference is organized into `macros/` (procedural macros a programmer invokes, including the type-level construction macros), `derives/` (the `#[derive(...)]` family), `attributes/` (modifier attributes consumed by a host macro), `components/` (the built-in CGP components — consumer/provider trait pairs defined with `#[cgp_component]`/`#[cgp_type]`/`#[cgp_getter]`), `providers/` (the zero-sized provider structs a context delegates to), `traits/` (capability and mechanism traits that are *not* themselves components), and `types/` (the type-level building-block types). A trait belongs in `components/` rather than `traits/` precisely when it is a CGP component with a generated provider trait and `…Component` marker. The high-level conceptual overviews that tie several constructs together are not per-construct reference documents; they live in the sibling top-level [concepts/](concepts/) directory and are cataloged in the [concepts index](concepts/README.md). The [reference index](reference/README.md) describes the reference layout in full and is the catalog you register a new reference document in. Because documents live in different subdirectories, a cross-link between two of them is a relative path — a sibling in the same directory is `name.md`, and a document in another directory is `../that-dir/name.md`. - -Cross-link generously. When a document mentions another construct, link to its reference document so a reader can follow the thread. A mention of a construct that is not yet documented is a useful signal of what to write next; record it as a gap in the [reference index](reference/README.md) — under the `traits/` or `types/` pending sections, for example — rather than leaving a dangling link with no home. - -## Syntax grammar conventions - -Every macro with custom syntax carries a **Syntax Grammar** section that formalizes what its Syntax section describes in prose, and the two must agree. "Custom syntax" means the macro's invocation accepts a bespoke grammar — an attribute argument with its own structure (`#[cgp_component]`, `#[cgp_impl]`), or a function-like macro body with a table, list, or path grammar (`delegate_components!`, `cgp_namespace!`, `Symbol!`, `Path!`). A macro whose invocation is just a plain Rust item with no arguments — `#[async_trait]`, `#[cgp_auto_dispatch]`, `#[cgp_auto_getter]` — has no custom syntax and gets no grammar section; an attribute that takes only a single optional identifier still does, because that identifier is the macro's own grammar. The grammar describes the *tokens the macro itself parses*: the attribute-argument tokens for an attribute macro, or the body tokens for a function-like macro — not the surrounding `#[...]` or `name!{...}` delimiters, and not the plain Rust item the attribute is applied to. - -Write the grammar in the notation of the [Rust Reference](https://doc.rust-lang.org/nightly/reference/notation.html), using the production-rule flavor of its [grammar dev-guide](https://rust-lang.github.io/reference/dev-guide/grammar.html). The rules are: - -- A production is written `Name -> Expression`, one rule per line, placed inside a fenced ` ```ebnf ` code block. -- A **nonterminal** is a CamelCase name referring to another production. A CamelCase name that the section does not define itself — `Type`, `Expression`, `Generics`, `GenericArgs`, `WhereClause`, `TypePath` — is a production of the Rust grammar, reused rather than re-specified. An `ALL_CAPS` name — `IDENTIFIER`, `STRING_LITERAL` — is a lexer token of the Rust grammar. -- A **terminal** is the exact characters to match, written in backticks: `` `new` ``, `` `:` ``, `` `=>` ``, `` `@` ``. -- `x?` is optional (zero or one); `x*` is zero or more; `x+` is one or more; `A | B` is alternation; `( … )` groups for precedence; juxtaposition is an ordered sequence. The less-common forms — character ranges `` [`a`-`z`] ``, exclusions `` ~[ … ] ``, negative lookahead `!x`, and `// line comments` — follow the Rust Reference and are used only when a construct genuinely needs them. - -Keep each grammar honest the same way the Expansion section is kept honest: it is a claim about what the macro's parser accepts, so verify it against the argument and body parsers in `cgp-macro-core` (the `args.rs`, `table.rs`, `key/`, `value/`, and `statement/` modules under `types//`) rather than transcribing the prose. When a macro shares a sub-grammar with another — `delegate_and_check_components!` reuses `delegate_components!`'s mappings and keys, and `cgp_namespace!` reuses the same key and path forms — define the shared production once in the macro that owns it and reference it by name from the others, exactly as the cross-linking rule requires, so the two grammars cannot drift apart. Follow the dual-reader prose style here too: open the section with a sentence naming what the grammar covers, then the code block, then a short paragraph explaining any nonterminal or constraint a reader could not infer from the rules alone (which keys are required, which options are mutually exclusive, what default fills an omitted value). - -## Reviewing and updating a document - -Reviewing a document means checking it against the code and then improving it, not just reading it. Approach a review in two passes: first confirm that every claim is still true, then improve how the document reads. The two passes are separate concerns — correctness is non-negotiable, while readability is a judgment call — and conflating them leads to polished prose that is subtly wrong. - -The source code is the single source of truth, above the skill and above the documents themselves. When the document, the `/cgp` skill, and the implementation disagree, the implementation wins every time: correct the document to match the code, and do not let the skill's teaching examples or the document's prior wording override what the macro actually does. The skill is a teaching aid whose simplified examples can lag the parser, so confirm any contested detail — an accepted syntax form, a default name, the shape of an expansion — directly against `cgp-macro-core`. Treat such a conflict as a defect in whichever artifact disagrees with the code, and prefer fixing the document you are reviewing. - -Verify specific behavior against the tests and the macro snapshots, then state the behavior in your own words without citing the test. The behavioral tests in [crates/tests/cgp-tests](../crates/tests/cgp-tests) and the expansion snapshots in [crates/tests/cgp-macro-tests](../crates/tests/cgp-macro-tests) are the most reliable evidence of what a macro does in a corner case — read them to confirm how a construct behaves, what it generates, and where it errors. The document, however, explains the behavior itself, not the test: write "a tuple struct keys its fields by `Index`," never "the test `tuple_struct.rs` shows that…". A reader of the reference should learn the semantics directly; the test is your evidence, not theirs. A reference document does not link to test files at all — the pointers into `crates/tests` (behavioral tests, failure cases, and expansion snapshots) belong in the construct's [implementation document](implementation/README.md), whose Tests and Snapshots sections are the canonical index of coverage. Link the reference's Source section to that implementation document rather than to any test. - -Record corner cases and confirmed bugs under the document's **Known issues** section. When a review uncovers behavior that is surprising, a sharp edge a user could trip on, or an outright bug in the construct, add a short prose note there describing what happens and, for a bug, what the correct behavior would be. Add the heading if the document does not yet have one, and place it just before Source. A known issue is documentation of reality, so describe the behavior as it currently is even when it is wrong — and when the bug is later fixed in the code, remove the note in the same change, per the synchronization rule. - -Improve flow and readability when a review finds the document hard to follow, especially after it has accumulated many small edits. A document that has been patched repeatedly tends to drift from the dual-reader prose style — topic sentences stop matching their paragraphs, the same concept gets explained in two places, and sections lose their order. When you see this, rewrite or reorganize the affected sections rather than adding yet another patch. A clean rewrite that preserves every verified fact is better than a document that is technically correct but no longer reads as a single coherent explanation. - -Remove stale content and deduplicate as part of every review. Delete claims the code no longer supports, drop examples that no longer compile, and collapse repeated explanations of the same concept into one place — ideally the document that owns that concept — replacing the duplicate with a cross-link. Two documents explaining the same mechanism in slightly different words will eventually disagree, so a single explanation plus a link is both shorter and safer. - -Re-check the document's place in the whole reference, not just its contents. Ask whether the change you are making implies a construct that deserves its own document — if a document keeps explaining another construct at length, that construct probably needs its own file and a cross-link. Ask whether the file is still in the right subdirectory and whether the set of files still divides cleanly; a subdirectory that has grown unwieldy or a document that has outgrown its category is a signal to reorganize. When you add, move, or split a document, update the [reference index](reference/README.md) and fix the affected cross-links in the same change, exactly as the synchronization rule requires. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index bfa13fc0..00000000 --- a/docs/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# CGP Knowledge Base - -Context-Generic Programming (CGP) is a language extension for Rust, with pluggable trait implementations at compile-time. In ordinary Rust a trait has one implementation per type; CGP lets one trait have many interchangeable implementations and lets each context choose which one it uses, through a small wiring table the compiler resolves statically — so the flexibility costs nothing at runtime. It is an ordinary library on stable Rust that desugars to plain traits and impls, adopted one component at a time, and it reaches beyond swappable implementations to abstract types each context picks for itself, extensible records and variants, and a family of composable handlers. - -This directory is a knowledge base about CGP, written by and for AI coding agents. Its purpose is to give an agent everything it needs to understand the *full semantics* of CGP — what each construct means, what code it expands into, and how the pieces fit together — without having to re-derive that understanding from the macro implementation every time. The `/cgp` skill gives a fast orientation; this knowledge base is the durable, version-controlled record that goes deeper and stays in sync with the code. - -## Why this exists - -CGP is implemented almost entirely as procedural macros, and proc-macro source code is a poor place to learn semantics from. An agent reading [crates/macros/cgp-macro-core](../crates/macros/cgp-macro-core) sees token-stream manipulation and AST transforms, not the meaning those transforms produce. The meaning — "`#[cgp_component]` generates a consumer trait, a provider trait, and two blanket impls that connect them" — has to be reconstructed by mentally running the macro. That reconstruction is slow, error-prone, and gets repeated on every visit. This knowledge base captures the reconstruction once, in prose, so the next agent can read the conclusion instead of re-deriving it. - -The knowledge base also serves as a contract. When an agent changes how a macro expands, the corresponding reference document is the place where the intended new behavior is stated in plain language. A reviewer can compare the prose against the code and against the expansion snapshots in [crates/tests/cgp-macro-tests](../crates/tests/cgp-macro-tests) to confirm that all three agree. Documentation that drifts out of sync with the code is worse than no documentation, so keeping these documents accurate is a hard requirement of any change — see [AGENTS.md](AGENTS.md) for the maintenance rules. - -## How it is organized - -The knowledge base is divided into several top-level sections, described below in no particular order, and will grow to contain more as the need arises. Each section answers a different question about CGP, so a reader picks the one that matches their need rather than reading them in sequence. - -The [reference/](reference/README.md) directory holds one document per CGP construct — one for `cgp_component`, one for `cgp_impl`, one for `delegate_components`, and so on. Each document is self-contained and explains a single construct completely: its purpose, its accepted syntax, the exact code it desugars to, worked examples, and links to the constructs it relates to. The [reference index](reference/README.md) lists every construct and tracks which ones are documented. - -The [concepts/](concepts/README.md) directory holds the cross-cutting conceptual overviews that span multiple constructs — the consumer/provider duality, dependency injection, namespaces, the handler family, and so on — each explaining one idea and linking down into the reference documents for the mechanics. Where the reference explains the individual trees, the concepts explain the shape of the forest. - -The [examples/](examples/README.md) directory holds self-contained worked examples, one realistic use case developed end to end per document, from its contexts and components through to the wiring that connects them. The examples are the canonical source of code snippets the reference and concept documents reuse, so the same running scenarios recur across the whole knowledge base. - -The [guides/](guides/README.md) directory holds the guides to *writing* CGP — documents that direct the choices an author makes when more than one construct could express the same thing. Where the reference and concepts explain what a construct means and why it exists, a guide is prescriptive: it recommends a default form, names the trade-offs of the alternatives, and usually walks a concrete before/after refactoring. Choosing a construct's vanilla-looking form over its explicit equivalent, keeping wiring tables short with namespaces, and debugging a wiring that will not compile all live here. - -The [errors/](errors/README.md) directory catalogs the compiler errors CGP produces *after* codegen — input a macro accepts and lowers to Rust that then fails to compile — organized by the kind of error rather than by the macro that produced it. Each document records the anatomy of one class: the mistake that triggers it, the shape of the diagnostic, whether the root cause is present in the output, and where it sits when it is. The catalog is built around the distinction between errors that *surface* their root cause and those the compiler *hides*, and it is the canonical documentation for the post-codegen compile-fail cases — now pinned as UI fixtures in [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's first-class error toolchain. Each class records both the raw diagnostic and how `cargo-cgp` reshapes it, so the catalog serves debugging agents, guides the tool, and doubles as the reference behind [`cargo-cgp`'s own](reference/cargo-cgp.md) `[CGP-Exxx]` output. Failures a macro raises by *rejecting* its input stay with the macro's implementation document instead. - -The [related-work/](related-work/README.md) directory looks outward instead of inward: each document takes an external concept, framework, or language feature that resembles CGP — dependency injection, implicit parameters, and so on — explains it faithfully and with citations, weighs what its users like and dislike about it, and positions CGP against it. These documents exist to serve future user-facing writing, giving an agent who must explain CGP to readers of a particular background the honest comparison and the positioning strategy to build on the intuitions those readers already hold. - -The [communication-strategy/](communication-strategy/README.md) directory turns that outward-facing material into guidance for *writing* about CGP to the public — landing pages, tutorials, articles, blog posts, social-media threads. Where a related-work document compares CGP to one external idea, a communication-strategy document generalizes across those comparisons into audience-level strategy: which readers exist and what each already believes, which content and hooks gain the most attention, which misunderstandings CGP reliably provokes and how to preempt them, and what tone keeps everything written about CGP reading as one voice. An agent preparing any public-facing writing reads here first, then draws on the matching related-work document for the depth of a particular comparison. - -## How to use it - -An agent working on CGP should read the relevant reference document before changing a construct, and should consult the `/cgp` skill for the conceptual framing that ties constructs together. The reference documents assume familiarity with the vocabulary the `/cgp` skill establishes — consumer traits, provider traits, providers, wiring, and so on — and focus on precise per-construct semantics rather than re-teaching the paradigm. Read the two together: the skill for the shape of the forest, the reference for the individual trees. diff --git a/docs/communication-strategy/AGENTS.md b/docs/communication-strategy/AGENTS.md deleted file mode 100644 index 44beca04..00000000 --- a/docs/communication-strategy/AGENTS.md +++ /dev/null @@ -1,41 +0,0 @@ -# AGENTS.md - -This file provides guidance to LLM agents when working with code in this repository. - -This directory holds the **communication-strategy** documents of the CGP knowledge base — the guidance an agent uses when writing anything public-facing about CGP, from a landing page or tutorial to a blog post, an article, or a social-media thread. Read the knowledge-base [README.md](../README.md) for the background on the whole base, the section's own [README.md](README.md) for what it covers, and the governing [../AGENTS.md](../AGENTS.md) for the rules every section shares. The rules below are specific to communication strategy, and they differ from the rest of the base in kind: the other sections record what CGP *is*, while this one records how to *present* it. - -## The roles you play here - -When you work in this directory, you are not a coding assistant transcribing facts — you are CGP's **marketing director** and **developer-relations lead**, and you are expected to contribute real expertise in those roles. Bring the judgment those jobs demand: audience psychology, positioning, message discipline, brand voice, community empathy, and an instinct for what earns attention versus what invites a pile-on. Do not defer these decisions or hedge them as "not a technical matter"; they *are* the matter here, and a vague or timid answer is a failure of the role the same way a wrong expansion is a failure in a reference document. Propose the pitch, name the framing, choose the words, and defend the choice. - -The two roles pull in complementary directions, and holding both is the point. The **marketing director** asks what makes CGP worth a reader's attention: which capability to lead with, which audience a piece targets, which one-line framing lands, and how to keep every piece of public writing sounding like one coherent voice. The **developer-relations lead** asks whether a developer will *trust* what they read: whether a claim survives contact with an expert, whether a cost is being hidden, whether the tone respects the reader's intelligence, and whether the community will feel courted or condescended to. Marketing without devrel produces hype that the Rust audience punishes on sight; devrel without marketing produces honest writing no one reads. Every document here must satisfy both. - -## What every communication-strategy document must do - -Each document turns audience knowledge into concrete, usable guidance for a future writer, and shares a set of obligations. A document that merely describes CGP without telling the writer how to *present* it has not done its job. - -- **Be prescriptive, not descriptive.** Say what to do: which selling point to lead with for which reader, which objection to defuse first, which exact words to prefer and which to avoid. A writer should be able to act on the document without re-deriving the strategy. -- **Ground every claim about audiences in the evidence.** The sentiment about what developers value and resent — about dependency injection, implicit parameters, type classes, effects, reflection — lives, cited, in the [related-work](../related-work/README.md) documents, and the facts about what the community measurably reads, discusses, and how CGP has actually been received live, cited, in [attention-and-engagement.md](attention-and-engagement.md). Draw on both and link to them rather than inventing reactions; when you assert that "the Rust audience distrusts macro magic" or that "a topic earns attention," it should trace to real, cited sentiment or a real, cited survey, thread, or post. External citations are concentrated in those two homes so the strategy documents stay in one voice; add a new external source there and link to it rather than scattering raw URLs across the section. -- **Keep every CGP claim true.** A selling point that names a capability CGP does not have, or a rebuttal that promises behavior it does not deliver, is the most damaging kind of error here, because it is shown to the audience most able to catch it. Every factual claim about CGP is bound by the [synchronization rule](../AGENTS.md) exactly as a reference document's Expansion is: verify it against the source and the `/cgp` skill, and prefer the modern idioms the skill and [guides](../guides/) teach. -- **Pair advantage with honesty.** Because the eventual reader is often a skeptic, name the cost beside the benefit wherever the audience will look for it. A document that only sells is a document devrel would reject. -- **Write for the marketing-naive expert.** These documents are read as much by human CGP advocates as by agents, and that reader is fluent in CGP but new to marketing, public communication, and developer relations — so calibrate the prose to that exact gap. Explain a non-technical concept the first time it appears, define the term of art, and anchor it in an intuition a systems programmer already holds; but never re-teach CGP itself, because the reader knows the technology far better than the craft. When you invoke a principle like positioning, framing, social proof, or the funnel, treat it as new to the reader and worth a plain sentence of explanation; when you name a CGP construct, treat it as familiar. The concentrated home for these definitions is [glossary.md](glossary.md) — introduce a term inline the first time a document leans on it, but link the glossary rather than re-defining a recurring term in every document, the way external citations concentrate in [attention-and-engagement.md](attention-and-engagement.md). The [README](README.md) states the audience and the general principles this calibration rests on. - -## Honesty is the strategy - -The single rule that governs everything here is that honesty *is* the marketing strategy, not a constraint on it. CGP's public audience is unusually able to detect spin — they are the practitioners of the very concepts CGP compares itself to — so an overclaim, a strawman of a competing tool, or a hidden cost does more damage than saying nothing. The guidance in these documents therefore always leads with a true, concrete capability, states it in the reader's own vocabulary, and concedes the genuine trade-offs, because that is what actually persuades this audience. When a piece of strategy tempts you toward exaggeration, treat the temptation as a signal that the honest version needs a better frame, not that the honest version needs to be abandoned. - -Two guardrails follow directly and are absolute. Never fabricate evidence — no invented benchmarks, adoption numbers, quotations, or version-specific claims; when a number or quote would strengthen a point, either source it or omit it. And never disparage another language, framework, or community to elevate CGP; the related-work documents set the standard of representing every compared tool as its own users would recognize it, and public writing must meet the same bar. Naming where a competing tool is simply the better choice is a devrel asset, not a concession. - -## Keeping the section in sync - -These documents sync against three moving targets rather than one, and a review checks all three. First, they sync against **CGP's actual capabilities**: a selling point or rebuttal that rests on a feature the code no longer has, or misses a capability newly added, is stale and must be corrected — the synchronization rule applies to every CGP claim. Second, they sync against **the related-work sentiment**: community attitudes evolve, features that were experimental ship and features that were praised fall out of favor, so when a related-work document's sentiment is revised, revisit the selling points and skepticisms that rest on it. Third, they sync against **the reader profiles**: [reader-profiles.md](reader-profiles.md) is the audience model the rest of the section builds on, so a change to who the readers are ripples into what to sell them and which objections to expect. - -A fourth sync target is the section's own vocabulary of the craft: a non-technical term of art introduced anywhere earns an entry in [glossary.md](glossary.md) in the same change, with a plain definition and a programmer's anchor, so the section keeps one home for these definitions the way it keeps one home for external citations. When a term's fuller treatment moves or is reworded — the funnel in the [README](README.md), positioning in [positioning.md](positioning.md), the audience-facing word list in [vocabulary.md](vocabulary.md) — check that the glossary's short gloss and its pointer still agree. - -The documents also sync against **each other**, and the tightest coupling is a three-way one: a single CGP claim shows up as a capability in [selling-points.md](selling-points.md), as the objection it provokes in [skepticism.md](skepticism.md), and as the pain it removes in [problems-solved.md](problems-solved.md). These are three views of the same thing — the reader who hears "compile-time dependency injection," asks "isn't DI heavy and magical?", and feels "I can't swap my mock for the real thing without a framework" is one reader — so a claim added to any of the three usually implies an entry in the other two, and an edit to one should check whether its mirrors need the same edit. Keep the wording across all of them consistent with [vocabulary.md](vocabulary.md), which is the consolidated authority on which words to use and avoid: a writer must never be told to prefer a phrase in one document that another warns against, and when the two disagree, the vocabulary list resolves it. [positioning.md](positioning.md) draws the same boundary the "why not just traits" skepticism draws, so keep the two aligned as well. - -## Document structure - -These are strategy documents, not reference documents, so they do not follow the reference template of Purpose/Syntax/Expansion. They follow the dual-reader prose style instead: open with a level-one heading naming the document and a one-sentence summary, open every section and subsection with a self-contained topic sentence, and frame every list with a sentence before it. What is distinctive here is that the writing is *about* wording, so quotable example phrasings are welcome and a short framed list of "say it like this / avoid this" is often the clearest form — use it freely, but frame it, and let the prose around it carry the reasoning. Prefer plain language and the knowledge base's established CGP vocabulary — consumer trait, provider trait, provider, wiring, impl-side dependency, context — so a reader moving between this section and the rest never reconciles two dialects. - -Register every new document in the catalog in [README.md](README.md) in the same change that adds it, and cross-link generously: to [reader-profiles.md](reader-profiles.md) for the audience a piece of guidance targets, to the [related-work](../related-work/README.md) documents for the sentiment and the comparison a claim rests on, to the [concepts](../concepts/README.md) for the CGP idea behind a selling point, and to the [reference](../reference/README.md) for the exact construct a claim names. diff --git a/docs/communication-strategy/README.md b/docs/communication-strategy/README.md deleted file mode 100644 index ea66e52a..00000000 --- a/docs/communication-strategy/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# CGP Communication Strategy - -This directory holds the communication-strategy documents of the CGP knowledge base — guidance for anyone preparing public-facing material about CGP, from a landing page or a tutorial to a blog post, an article, or a social-media thread. Where the rest of the knowledge base records what CGP *is*, this section records how to *present* it: which ideas to lead with, how to frame the features that will feel unfamiliar, which misunderstandings to head off, and what tone to keep so that everything written about CGP reads as one consistent voice. - -These documents are written by AI agents, but their readers are as much human as machine. The intended human reader is a **CGP advocate** — someone who understands CGP deeply and wants to promote it — and the defining assumption about that reader is that their expertise is lopsided: strong in the technology, and often near-zero in the domains that decide whether the technology is ever heard about. A gifted CGP practitioner is not automatically a good explainer of CGP, and is usually the opposite, for the reason the principles section below explains. So these documents deliberately teach the non-technical craft — marketing, public communication, developer relations — to a reader who has never studied it, spelling out principles a marketer takes for granted and grounding them in terms a systems programmer already trusts. An LLM agent drafting public material and a human advocate promoting the project turn to the same documents for the same reason: to borrow judgment they do not natively have. - -## Why this exists - -CGP adds language-level capabilities that many Rust developers have never seen — a consumer/provider trait split, per-context wiring, impl-side dependencies, type-level tables — and the same true sentence about them can excite one reader and alienate another. A functional programmer hears "overlapping instances made safe" as a gift; a pragmatic Rust engineer hears the same pitch as a warning that the crate is too clever. Public-facing writing therefore succeeds or fails on framing as much as on accuracy, and framing depends on knowing who is reading and what they already believe. This section captures that knowledge once, so an advocate or agent preparing public writing starts from a shared understanding of the audience and a shared set of positioning decisions, rather than re-deriving them for every post — and so the whole body of CGP's public material speaks with one voice. - -The purpose is twofold: to help writing **gain attention**, by naming which content and hooks draw the most interest, and to help it **minimize misunderstanding**, by naming the misreadings CGP reliably provokes and how to preempt them. Both goals run through the reader: attention is won by leading with what a specific audience cares about, and misunderstanding is avoided by defusing the specific misreading that audience is prone to. - -One development is worth flagging at the section level, because it shifts a long-standing piece of CGP's positioning. The obstacle CGP's audience has cited most is not conceptual but practical: its compiler errors, where a small wiring mistake expands into a wall of generated-type noise with the real cause buried or hidden. That obstacle now has a first-class answer — [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), a cargo subcommand that rewrites those errors into a compact, root-cause-first form (see the [cargo-cgp reference](../reference/cargo-cgp.md) and the [error catalog](../errors/README.md)). For an advocate this is a de-risking asset to lead with, not a cost to quietly concede — subject to the one honesty caveat the whole section is bound by: cargo-cgp is an early pre-release that already handles the common wiring errors but does not yet reshape every class, so present it as *the error experience is dramatically better and actively improving*, never as solved. The [skepticism](skepticism.md), [problems solved](problems-solved.md), [selling points](selling-points.md), and [technical barriers](technical-barriers.md) documents work this shift out in detail. - -## Relationship to related work - -This section is the natural companion to [related-work/](../related-work/README.md), and the two are read together when preparing public writing. A related-work document explains one external idea — dependency injection, type classes, reflection — faithfully, records what its users like and dislike, and positions CGP against it; a communication-strategy document generalizes across those comparisons into audience-level guidance about which readers exist, what they already believe, and how a piece should be shaped for them. When a related-work document records a sentiment — that Rust developers reach for Dagger to escape reflection's runtime cost, say — this section turns it into a reader trait an author can plan around. Read the matching related-work document for the depth of a comparison; read here for the shape of the audience. - -## Principles from marketing, public communication, and developer relations - -Promoting CGP well depends on a body of knowledge that has nothing to do with CGP, and this section makes that knowledge explicit for a reader who has never needed it. The documents in the catalog apply these principles to CGP's specific case; what follows here are the principles themselves, drawn from marketing, public communication, and developer relations, stated plainly for a technical reader and anchored where possible in an intuition a programmer already holds. None of it is secret or hard — it is simply a separate discipline, with its own rules, that being excellent at CGP does not confer. - -The one principle to internalize before all the others is the **curse of knowledge**: the more completely you understand CGP, the worse your instinct for explaining it to someone who does not. Expertise erases the memory of confusion, so the expert leads with the mechanism they find elegant, uses vocabulary that is precise to them and opaque to everyone else, and skips the motivating problem because it feels too obvious to say out loud. Nearly every failure mode this section warns against traces back to this one, and the correction is always the same: write for the reader's current knowledge, not your own. A CGP advocate is the highest-risk author precisely because they know the most, so treat your own fluency as a bias to correct rather than an advantage to lean on. - -The terms these principles use — and the ones the rest of the section reaches for, from *positioning* and *framing* to *the funnel* and a *call to action* — are collected, with a plain definition and a programmer's analogy for each, in the [glossary of the non-technical craft](glossary.md). Read it as the quick-reference companion to this section: where the principles below are the argument, the glossary is the lookup for any single word in it that is unfamiliar. - -### Marketing: positioning and attention - -Marketing, stripped of its reputation, is the discipline of getting the right idea into the right head in the right words, and its first law is that the reader — not the author — decides what a thing *is*. A reader meeting CGP does not build an understanding from scratch; they pattern-match it, in seconds, to the nearest category they already know ("oh, it's a DI framework," "it's macro magic," "it's academic"), and that snap category, not your careful explanation, is what they remember and repeat. **Positioning** is the deliberate act of choosing that category for them before they choose a dismissive one, which is why the wording of a one-liner matters out of all proportion to its length. A handful of consequences follow and recur throughout the section. - -- **Lead with the benefit, not the feature.** A reader cares about the problem you remove, not the mechanism you use to remove it — a well-worn marketing adage holds that no one wants a drill, they want a hole. For CGP this means opening on a pain a developer already feels, never on the consumer/provider split. -- **Attention is scarce and adversarial.** A reader gives a new project seconds before continuing or dismissing it, so the first line carries most of the message and the rest is read only if the first line earns it. You are not competing with a rival crate for attention; you are competing with the reader closing the tab. -- **Framing decides the reaction.** The same true fact, worded two ways, produces opposite responses — "overlapping instances made safe" delights one reader and alarms another — so the choice of frame is not decoration on the message, it *is* the message. -- **Differentiation answers "why this and not that."** A reader who sees no difference from a tool they already have will keep the tool they already have, so a pitch must name what CGP does that the obvious alternative cannot. - -### Public communication: clarity and consistency - -Public communication is the craft of being understood at scale by people you will never meet, and it rewards nearly the opposite of what technical writing trains. Precision and completeness, the virtues of a specification, are liabilities in a pitch: a reader skimming a landing page needs the one idea that matters, not the exhaustive truth, and burying the point under qualifications loses them. The governing skills are clarity and consistency, and they compound across the whole body of writing rather than living in any single piece. - -- **Write for one reader, not everyone.** A message aimed at "all Rust developers" reaches no one, because the framing that wins the pragmatist loses the enthusiast; naming a single target reader is the first decision in a piece, ahead of the outline. -- **Prefer clarity over completeness.** Say less, clearly. One vivid idea a reader keeps beats five accurate ideas they forget, and an omitted nuance can be added later while a lost reader cannot be recovered. -- **Make it concrete, and make it a story.** People remember a concrete example and a problem-tension-resolution narrative far better than an abstract claim or a feature list, so a before-and-after on real code outperforms any adjective. -- **Keep one voice.** Consistency is what turns scattered posts into a recognizable project: when every piece uses the same words for the same ideas, each reinforces the others, and when they drift, the reader senses incoherence even if they cannot name it. This is why the section fixes a shared vocabulary rather than letting each author invent one. - -### Developer relations: trust and community - -Developer relations is communication with an audience that has been marketed to badly its whole career and has grown expert at detecting it, so its rules invert the naive ones: this audience rewards restraint and punishes hype, and its trust, once lost, is not won back by the next post. Trust is the entire currency, which is why **honesty is not a constraint on the strategy but the strategy itself** — an overclaim, a strawman of a competing tool, or a hidden cost does more damage than saying nothing, because the reader who catches it discounts everything else you say, often loudly and in public. The principles below are what earn and keep that trust. - -- **Show, don't tell — and let peers do the telling.** A developer believes running code and another developer's experience far more than a vendor's adjective, so demonstrate a capability rather than asserting it, and remember that **social proof** — a real system built with CGP, a peer vouching for it — persuades where self-praise cannot. -- **Concede the costs.** Naming a genuine limitation, and naming where a simpler tool is the better choice, is not a weakness in the pitch; with this audience it is what makes the rest of the pitch believable. The advocate who declines to oversell is the one who is believed. -- **Meet developers where they are, and respect their time and intelligence.** Condescension and hype both read as disrespect; the tone that works is a knowledgeable peer explaining something useful, neither talking down nor puffing up. -- **Mind the funnel, and match the ask to the reader's stage.** A reader moves through stages — first hearing of CGP, growing curious, trying it, adopting it, advocating for it — and each stage wants different content and a different next step, so asking a first-time reader to bet a codebase on CGP fails as surely as handing a ready adopter another elevator pitch. -- **Beware the pile-on.** A technical community can turn collectively, and a single post that reads as arrogant or dishonest can trigger a public dunking that outlives it; the defensive move is the same as the honest one — claim only what is true, and never disparage another tool to elevate CGP. - -Taken together, these principles are the foundation the rest of the section stands on, and the catalog below is largely them worked out in CGP's specific case: the [reader profiles](reader-profiles.md) apply "write for one reader," the [selling points](selling-points.md) and [problems solved](problems-solved.md) apply "lead with the benefit," the [skepticism](skepticism.md) and [positioning](positioning.md) documents apply "concede the costs" and "trust is the currency," the [tag lines](tag-lines.md) and [key features](key-features.md) apply "positioning" and "attention is scarce," the [technical barriers](technical-barriers.md) document is the curse of knowledge answered move by move, and the [vocabulary](vocabulary.md) document enforces "keep one voice"; the [worked examples](worked-examples.md) then show every one of these principles operating at once on finished copy. A reader who absorbs the principles here will recognize them everywhere below. - -## The catalog - -The documents below inform how CGP is presented to the public; register a new one here in the same change that adds it. The authoring rules for the section — including the marketing-director and developer-relations roles an agent takes on here — live in [AGENTS.md](AGENTS.md). - -- [Reader profiles](reader-profiles.md) — the kinds of readers public-facing CGP writing must serve, from newcomers to Rust through advanced developers and across the backgrounds they arrive from, with what each reader already knows, is excited by, grows skeptical of, and needs from a piece written for them. -- [Selling points](selling-points.md) — the true capabilities CGP should advertise, each with the phrasings that make it land and the phrasings that backfire, plus audience-tuned one-liners keyed to the reader profiles and the related-work comparisons. -- [Skepticism](skepticism.md) — the objections a reader brings to CGP, whether imported from a paradigm they distrust or native to the Rust community, judged for whether they are justified and answered with wording that convinces without triggering the misunderstanding that fed them. -- [Tag lines](tag-lines.md) — CGP's chosen one-line description — "a language extension for Rust, with pluggable trait implementations at compile-time" — analyzed word by word against attention, skepticism, and honest feasibility, with the retired "modular programming paradigm" kept as historical background, the high-level pitch that should follow the line, and model introductions for a new audience. -- [Key features](key-features.md) — the short, curated headline set of the few best selling points to put on the front page, with titles and one-line copy chosen for breadth, honesty, and the phrasing lessons of the rest of the section. -- [Technical barriers](technical-barriers.md) — the comprehension barriers a reader hits when learning CGP, from unfamiliarity with generics and traits upward, and the design affordances and progressive-disclosure teaching moves that lower each one. -- [Attention and engagement](attention-and-engagement.md) — the evidence base for where the Rust community's attention actually sits, what CGP's own public reception has been, and which conversations and channels a piece should attach to; the engagement counterpart to related-work and the home of the section's external citations. -- [Problems solved](problems-solved.md) — the concrete problems CGP removes, written as short before-and-after stories a writer leads with so a piece opens on a pain the reader already feels; the pain axis that complements the capabilities in selling-points and the objections in skepticism. -- [Positioning](positioning.md) — the honest decision guide for when to reach for CGP and when a plainer tool wins, alternative by alternative, so a piece draws CGP's boundary in public rather than letting a skeptic draw it. -- [Formats and channels](formats.md) — per-artifact playbooks for the launch post, tutorial, README, talk, thread, and comparison, each with its opening move, length, the dismissal to preempt, and the call to action, plus the conversion ladder that matches the ask to the reader. -- [Worked examples](worked-examples.md) — the section's guidance assembled into finished, annotated model drafts of a launch post, a README above the fold, and a social thread, each followed by a note tracing every move back to the document that argues for it; the "show, don't tell" companion that lets a writer copy the moves rather than re-derive them. -- [Vocabulary and message discipline](vocabulary.md) — the canonical word list for public writing: the term to use for each concept, the plain-language gloss that introduces it, the terms to defer, and the words and framings to avoid, so everything reads as one voice. -- [Glossary of the non-technical craft](glossary.md) — plain-language definitions of the marketing, public-communication, and developer-relations terms of art the rest of the section uses, each anchored to an intuition a systems programmer already holds; the quick-reference companion to the principles above, and distinct from the vocabulary list, which governs the words used *about* CGP rather than the words of the craft itself. diff --git a/docs/communication-strategy/attention-and-engagement.md b/docs/communication-strategy/attention-and-engagement.md deleted file mode 100644 index 4005a172..00000000 --- a/docs/communication-strategy/attention-and-engagement.md +++ /dev/null @@ -1,54 +0,0 @@ -# Attention and engagement - -This document grounds the section's attention goal in evidence: where the Rust community's attention actually sits, what CGP's own public reception has been so far, and what both facts imply for the hooks and channels a piece should choose. - -## Why the strategy needs engagement evidence - -Attention is an empirical fact, not a matter of taste, so the decision of what to lead with should rest on what the Rust community measurably reads, upvotes, and argues about. This document is the engagement counterpart to [related-work](../related-work/README.md): where a related-work document carries cited sentiment about the *concepts* CGP compares itself to, this one carries cited facts about the *community* CGP is writing for, so a claim about what earns attention traces to a real survey, thread, or post rather than to a hunch. It is therefore the home of the section's external citations — the other strategy documents link here for the evidence behind an audience claim, the way they link to related-work for the evidence behind a concept claim. Community attention shifts, so this document is a sync target in its own right: when the survey or the discourse moves, the hooks that rest on it must be revisited. - -A caution on reading the evidence: public engagement metrics are noisy, and absence of a discussion is not proof of absence of interest. The findings below are strong enough to steer framing decisions, but they are inputs to judgment, not verdicts, and the closing section treats publication itself as the real measurement. - -## What the Rust community worries about — and what it rewards - -The clearest signal of where attention sits is the annual survey, and it names two costs to concede and one benefit to lean on. The [2024 State of Rust survey](https://blog.rust-lang.org/2025/02/13/2024-State-Of-Rust-Survey-results/) reports that slow compile times remain the perennial top pain, that subpar debugging support is among the leading tooling complaints, and that 45.2% of respondents named the language's growing *complexity* as a worry for its future — while, asked to prioritize the project's work, developers ranked runtime performance second only to fixing compiler bugs. Read together, these dictate three moves for a CGP writer. - -- **Concede compile-time cost and verbose diagnostics early and plainly.** They are the community's live sore spots, and a reader is actively scanning a new abstraction for whether it worsens them, exactly as [skepticism.md](skepticism.md) prescribes. A piece that stays silent on them reads as either naive or evasive to the survey's respondents. On the diagnostics, that concession now travels with a response — CGP's [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) toolchain reshapes the recognized error classes to lead with the root cause — though it remains a young pre-release, so the concession is not retired, only paired with the fix. -- **Treat "this adds complexity" as the most dangerous perception a piece can leave.** Complexity is the community's named fear for the language's future, so "still ordinary Rust," gradual adoption, and problem-first restraint are not merely pleasant framings — they are the direct answer to the audience's stated anxiety, and the reason [key-features.md](key-features.md) reserves a headline slot for "Still Ordinary Rust." -- **Lean hard on zero runtime cost.** The survey shows the community explicitly prizes runtime performance, so "resolved at compile time and compiled to a direct call" lands as an answer to something they already care about, not as an abstract virtue. This is the empirical backing for the [zero-runtime-cost selling point](selling-points.md). - -## The conversations that draw attention - -Some topics reliably draw the Rust community's attention, and a piece that attaches CGP to a live conversation borrows its energy — provided the attachment is honest. Four are worth naming, each with the way CGP connects and the overclaim to avoid. - -- **The orphan rule and overlapping implementations.** This is a durable, recurring frustration, with a repository dedicated to cataloguing its design problems ([Ixrec/rust-orphan-rules](https://github.com/Ixrec/rust-orphan-rules)) and a steady stream of posts on the newtype workaround. It is CGP's strongest attachment point because the pain is concrete and widely felt; lead the working-developer and type-system audiences here rather than on the paradigm. -- **Async and function coloring.** The [function-coloring debate](https://www.thecodedmessage.com/posts/async-colors/) recurs whenever async Rust is discussed, and async `fn` in traits carries its own well-known friction around [`Send` bounds and `dyn`](https://github.com/rust-lang/rust/issues/103854). CGP's handler family and its `Send`-recovery pattern touch this, but the honest attachment is narrow — CGP does not remove function coloring, and a piece must not imply it does. -- **Error handling.** The `anyhow`-versus-`thiserror` question is among the most-written-about topics in Rust, and CGP's abstract error type speaks to it directly. This is a strong, low-controversy hook for the working developer, framed as "abstract over the error type so fallible code never commits to one," per the [abstract-types selling point](selling-points.md). -- **Reflection and compile-time introspection.** This is a live, high-attention, officially-pursued area: `facet` drew wide interest, `bevy_reflect` is established, and the Rust project has a [reflection-and-comptime goal](https://rust-lang.github.io/rust-project-goals/2026/reflection-and-comptime.html) with a landed MVP. CGP's "compile-time structural reflection encoded in types" sits squarely in this conversation, which makes it timely — but because first-class compile-time reflection is coming to the language itself, position CGP as *available today* and *type-level and checked*, complementary to the built-in facility rather than a competitor the language will soon obsolete. - -## The pains CGP removes are real — and developers already hand-roll them - -The most persuasive evidence for a selling point is that developers reinvent CGP's mechanism on their own, and for the central capability they demonstrably do. The pattern CGP is built on — zero-sized marker types plus a helper trait, so several otherwise-overlapping blanket implementations can coexist — has been [independently discovered and blogged](https://www.greyblake.com/blog/alternative-blanket-implementations-for-single-rust-trait/) by a Rust author who reached it to work around the exact "no two blanket impls may overlap" limitation CGP exists to lift, describing the hand-rolled version as "3 extra lines to link things together." This is the single most useful fact in this document for the marketing role: the strongest selling point is not a capability the reader must be talked into wanting, but one they have already built by hand and would rather not maintain. Point to the reinvention as evidence, and the "isn't this over-engineered" reflex softens, because the reader recognizes their own workaround. - -The neighboring pains are evidenced too, and each sharpens a selling point. The dependency-injection frameworks Rust does have are niche and criticized on their own forums — [shaku](https://users.rust-lang.org/t/comparing-dependency-injection-libraries-shaku-nject/102619), for instance, for forcing `Arc` and for being unable to declare multiple implementations per component — which is precisely the limitation CGP's per-context wiring removes, so the honest pitch to that reader is "many implementations per context, no `dyn`," grounded in a complaint they can find themselves. And even hand-rolled trait-based DI leaks: as a [widely-cited post](https://jmmv.dev/2022/04/rust-traits-and-dependency-injection.html) documents, any type named in a public trait's method signature must itself be public, so trait-based injection quietly breaks encapsulation — a concrete cost that CGP's impl-side dependencies avoid, and a selling point [selling-points.md](selling-points.md) now carries. - -## CGP's own reception, and the lessons in it - -CGP's ideas do get discussed, but the reception is niche and runs mixed-to-skeptical, and reading it correctly starts with knowing which venue to trust. The substantive discussion happens on [Lobsters](https://lobste.rs/) and the [Rust subreddit](https://www.reddit.com/r/rust/), not on Hacker News: an HN submission is hit-or-miss, drawing almost no engagement unless it reaches the front page, so a low HN score reflects a submission's title and timing far more than the idea's reception and must not be reasoned from. On Lobsters, where the vote counts are small but the discussion is real, the original [Context-Generic Programming thread](https://lobste.rs/s/a5wfid/context_generic_programming) drew nineteen comments and the [v0.7.0 implicit-arguments thread](https://lobste.rs/s/6gcdzl/supercharge_rust_functions_with) twelve — enough to read what the skepticism actually clusters on, which is where the lessons are. - -Five patterns recur in that discussion, and each maps to guidance elsewhere in the section. - -- **Verbosity and "what problem justifies this."** The most common reaction is that CGP looks like "a very verbose boilerplate," with even an experienced Rust programmer reporting they struggled to follow the samples and asking what concrete problem warrants the complexity. This is the over-engineering reflex from [skepticism.md](skepticism.md) firing in the wild, and it confirms the prescription: lead with a concrete pain and a before/after, never with the paradigm. -- **Traceability of control flow.** A distinct, repeated objection is that the indirection makes a codebase hard to navigate, because it is "basically impossible to tell which particular bit of code will be entered on a method call." This is not the generic "macros are magic" complaint but a specific worry about following execution, now recorded as its own entry in [skepticism.md](skepticism.md); the answer is that the wiring table is the one explicit, greppable place naming the provider for each component. -- **"Isn't this just X reinvented."** Knowledgeable readers reach for prior art — aspect-oriented programming, Microsoft's COM, the ML module system (one comment invoked Greenspun's tenth rule) — as a skeptical frame. These deserve the honest engagement the [related-work](../related-work/README.md) documents supply, not deflection, because the reader making the comparison is exactly the one who can be won or lost on it. -- **The name does not communicate.** Multiple commenters said plainly that "context-generic programming" obscures more than it conveys, that "coining a new phrase makes it harder to understand," and reached instead for "structural typing" or "duck typing for statically-typed code" to name what they thought was being described. This is direct field validation of the [tag-lines.md](tag-lines.md) rule that a name is not a pitch, and it surfaces a community-supplied bridge term worth testing — with the caveat that CGP is nominal-and-wired, not truly structural. -- **Do not overstate the ergonomics.** When CGP claims a reader need not understand its internals, a skeptic answers that "when I hit a compilation error, I'm going to have to understand the desugaring," and they are right. The honest position, per [technical-barriers.md](technical-barriers.md) and [skepticism.md](skepticism.md), concedes the error-message barrier rather than promising the internals stay out of sight. This is the complaint CGP has answered most directly since: [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's error toolchain, exists specifically to un-hide and lead with the root cause of a CGP compile error, so a piece meeting this objection can now point to a deliberate response rather than only conceding the cost. The tool is new — v0.1.0-alpha — and has no reception evidence of its own yet; do not manufacture any, and point to it as the deliberate answer to a recorded complaint, not as something the community is already praising. - -Two further lessons come from outside that discussion and still hold. "Dependency injection" is not a safe general hook, because idiomatic Rust already does lightweight DI with traits and generics and a large part of the audience treats DI *frameworks* as an unwanted import — the distinct native objection now in [skepticism.md](skepticism.md). And social proof is worth building: the comparable success stories in adjacent ecosystems turned on a flagship adopter, the way TypeScript's adoption accelerated once a major framework endorsed it, so CGP's most convincing answer to the evaluator's "is anyone really using this" is a real, non-trivial system built with it and shown as a worked example, not more argument. - -## Where the profiles gather - -Attention is channel-specific, so the hook should be chosen for where a piece will actually appear, matching the [reader profiles](reader-profiles.md) to the room. The pragmatic majority and the first-contact skimmer dominate the general channels, and for CGP the discussion has actually landed on [Lobsters](https://lobste.rs/) and the [Rust subreddit](https://www.reddit.com/r/rust/), with the weekly *This Week in Rust* as steady distribution; Hacker News is worth submitting to but hit-or-miss, and matters only when a post reaches the front page, so it should never be a plan's linchpin. In those channels a front-page tag line and a concrete before/after must do the work, and the verbosity, traceability, and "just macros" framings must be preempted in the opening lines. The type-system and functional-programming audience gathers in more specialized corners, where the audience-tuned one-liners in [selling-points.md](selling-points.md) — type classes without the orphan rule, functors with the plumbing automated — land without translation. The evaluator reads long-form: a design document, a detailed write-up, a comparison table, where candor about maturity and cost is what persuades. Pick the channel, then the profile, then the hook — in that order. - -## Reading the reaction - -Because attention is empirical, the real grade of any hook is the reaction it draws, so the section should treat publication as a measurement rather than a conclusion. A hook is working when it draws questions about *how* CGP achieves something; it is failing when the top replies are the dismissals this audience actually reaches for — the ones observed in CGP's own threads above: "verbose / over-engineered," "I can't tell what code runs," "isn't this just AOP / COM / ML modules reinvented," and "the name tells me nothing," alongside the imported "just macros" and "another DI framework." Those replies are not noise but signal that the framing let the reflex fire before the novel part landed, and the fix is upstream, in the opening lines, per [reader-profiles.md](reader-profiles.md) and [tag-lines.md](tag-lines.md). Float a candidate hook where the target profile actually gathers, watch which dismissal it attracts, and revise toward the framing that draws the *how* question instead — the chosen tag line in [tag-lines.md](tag-lines.md) is offered for exactly this validation. diff --git a/docs/communication-strategy/formats.md b/docs/communication-strategy/formats.md deleted file mode 100644 index 39890c8a..00000000 --- a/docs/communication-strategy/formats.md +++ /dev/null @@ -1,46 +0,0 @@ -# Formats and channels - -This document turns the audience knowledge in the rest of the section into per-artifact playbooks: for each kind of public writing — a launch post, a tutorial, a README, a talk, a thread, a comparison — the opening move to make, the length to hold to, the dismissal to preempt first, and the action to ask for. - -## How to use this document - -A piece of CGP writing is shaped by its format as much as by its message, so the first decisions are made in a fixed order: pick the channel and the reader before the words. [attention-and-engagement.md](attention-and-engagement.md) says where the audience actually gathers and what earns its attention; [reader-profiles.md](reader-profiles.md) says who is reading; this document says how to structure the artifact once those are chosen. The order is channel → profile → format → hook, and skipping it is how a piece ends up written for no one in a form its channel punishes. - -Every playbook below shares four moving parts, and naming them once keeps each entry short. The **opening move** is the first thing the reader meets and the only thing a skimmer may read, so it carries a concrete pain or a runnable example, never the paradigm. The **length and depth** are set by the channel's patience and the reader's place on the [prerequisite ladder](technical-barriers.md). The **dismissal to preempt** is the reflex that format's audience fires first — "verbose / over-engineered," "just macros," "another DI framework," "the name says nothing" — which must be defused in the opening lines, because it is answered upstream or not at all. And the **call to action** is the single next step the piece asks for, matched to what that reader is ready to do; the conversion ladder at the end sets those out. These moving parts and the other terms of the craft that recur below — hook, tag line, above the fold — are defined in the [glossary](glossary.md) for a reader meeting them for the first time. - -## The link-aggregator launch post (Lobsters and r/rust) - -This is where CGP is actually discussed, so it is the format to get right first, and its reader is the pragmatic skimmer who will form and broadcast a snap judgment. Title it with the concrete-capability half of the tag line, not the paradigm name — "pluggable trait implementations for Rust, at compile-time" over "context-generic programming" — because the title is the whole pitch for most of the audience, per [tag-lines.md](tag-lines.md). Open the body on a runnable `Hello World` or a five-line before/after from [problems-solved.md](problems-solved.md); the one piece of feedback CGP's own launch received asked for exactly this ([attention-and-engagement.md](attention-and-engagement.md)), and it is what the format rewards. Keep it short and let the linked material carry the depth. Preempt the two dismissals this audience fires fastest — "verbose / over-engineered" and "why not just traits" — in the first paragraph, by naming the concrete pain that justifies the machinery and conceding where a plain trait would suffice, per [positioning.md](positioning.md). Then be present in the thread: this channel expects the author to answer, and the canned responses below are what to keep ready. The call to action is the quickstart, not the full paradigm. - -## The blog tutorial or deep-dive - -A tutorial has the reader's sustained attention but must earn each step, so it lives or dies on progressive disclosure. Open on a concrete problem the reader already has — a mock swapped for a real implementation, a trait grown monolithic — never on the consumer/provider split or coherence, per [technical-barriers.md](technical-barriers.md). Lead with the vanilla-looking idioms ([`#[cgp_fn]`](../reference/macros/cgp_fn.md), [`#[cgp_impl]`](../reference/macros/cgp_impl.md), [`#[implicit]`](../reference/attributes/implicit.md)) so early code reads like ordinary Rust, and reveal the machinery — the [`DelegateComponent`](../reference/traits/delegate_component.md) table, the desugaring — only when a reader has a reason to want it. Carry one running example the whole way through rather than switching examples per concept, so understanding compounds. Set the error-message expectation honestly before the reader hits one: CGP's diagnostics can be verbose, and a [`check_components!`](../reference/macros/check_components.md) localizes them. The call to action is the next tutorial or the [CGP Patterns book](https://patterns.contextgeneric.dev/), the depth a motivated reader graduates into. - -## The README and landing page - -The README's job is the first screen, where an evaluator and a skimmer both decide in seconds whether to continue. Put the tag line at the top as the descriptor beneath the project name — "a language extension for Rust, with pluggable trait implementations at compile-time" — with a reassurance line under it that heads off the runtime-container and new-language misreadings, per [tag-lines.md](tag-lines.md) — then the small headline set from [key-features.md](key-features.md), then a runnable `Hello World` above the fold (the first screen, before the reader scrolls), because a reader wants to see the code before the prose. Keep the feature set to the ruthless few; a list of ten reads as a list of none. State "a library on stable Rust" and the install line early, since the [evaluator](reader-profiles.md) is scanning for the toolchain gamble. The call to action is the quickstart for the skimmer and a link to the honest maturity-and-adoption discussion for the evaluator. - -## The conference talk or video - -A talk is the format with the most room to motivate, and its audience forgives depth if the *why* comes first. Open on the pain and the before/after, spend the middle on the single "aha" — that the same call resolves to a different implementation per context, chosen in one readable line and compiled to a direct call — and defer the coherence theory to a late "how it works" section for the audience that stayed for it. Show the vanilla-looking code on the first slides and the machinery only after the value has landed. Close on honest limits and where *not* to reach for CGP, because a talk that concedes its boundaries is the one an evaluator trusts. The call to action is the quickstart and the repository. - -## The social thread - -A thread on Mastodon, Bluesky, or X has seconds of attention and one job: earn the click without inviting the dunk. Lead with one concrete pain or one striking before/after image, keep jargon out of the first post entirely, and let a single link carry the depth. Do not open on the paradigm name, and do not compress the pitch so far that it reads as an overclaim — the skimmer who feels oversold broadcasts that reaction. This format is a hook delivery mechanism, not a place to explain; the call to action is a link to the post or quickstart that can. - -## The comparison or positioning piece - -A comparison earns disproportionate trust when it is honest and disproportionate damage when it is not, so its rule is the section's non-negotiable one: never disparage the compared tool, and name where it is simply the better choice. Represent every alternative as its own users would recognize it, drawing the sentiment and the mapping from the [related-work](../related-work/README.md) documents, and route the "when to use which" judgment through [positioning.md](positioning.md). A comparison table is a strong form here, provided each row concedes the case where the other tool wins; a table that shows CGP winning every row reads as a strawman and loses the reader it most wanted. The call to action is the honest decision guide, not a verdict. - -## Answering in threads - -Replying in a discussion is its own format, and because the general channels expect the author present, a small set of ready answers is worth keeping — each conceding the real cost before making the point, in the wording [skepticism.md](skepticism.md) fixes. The recurring questions and the shape of their answers: - -- **"Isn't this just a DI framework / macros / over-engineering?"** Concede that Rust needs no DI *framework* and that CGP is not one, then locate the concrete case that plain traits handle awkwardly — many implementations per context, an orphan-rule escape, a leaked-internals fix — per [positioning.md](positioning.md). -- **"How do I tell what code actually runs?"** Concede the indirection, then point at the wiring table as the one greppable place that names the provider for each component, per [skepticism.md](skepticism.md). -- **"What about compile times / the error messages?"** Concede both directions honestly, decline to invent a number, and name [`check_components!`](../reference/macros/check_components.md) and the compile-time-versus-runtime trade as the real mitigations. -- **"Why not just use traits?"** Agree that for one implementation, or a closed set, the plain tool wins, and point at the [modularity hierarchy](../concepts/modularity-hierarchy.md) as the map of when to climb. - -## The conversion ladder - -Every format ends by asking for a next step, and the right step depends on the reader, so the call to action is chosen from the profile, not fixed. The **first-contact skimmer** is ready only for a low-commitment look — the quickstart, a runnable example, the repository — so ask for that and nothing heavier. The **working developer** is ready to try CGP on a real problem, so point them at the [problems-solved](problems-solved.md) narrative closest to their pain and the guide that walks it. The **evaluator** is deciding on risk, so offer the honest maturity discussion, the incremental-adoption story, and a real system built with CGP as social proof — the flagship-adopter evidence [attention-and-engagement.md](attention-and-engagement.md) argues is worth building. And the **enthusiast** is ready to go deep, so hand them the [CGP Patterns book](https://patterns.contextgeneric.dev/) and the contribution path. Asking a reader for more than their rung is ready for is how a piece that earned attention loses the conversion. diff --git a/docs/communication-strategy/glossary.md b/docs/communication-strategy/glossary.md deleted file mode 100644 index faf80121..00000000 --- a/docs/communication-strategy/glossary.md +++ /dev/null @@ -1,51 +0,0 @@ -# Glossary of the non-technical craft - -This document defines, in plain language, the marketing, public-communication, and developer-relations terms of art that the rest of the section uses — each anchored to an intuition a systems programmer already holds — so a reader fluent in CGP but new to these disciplines never has to guess what a word means. - -## How to use this glossary - -This is the quick-reference companion to the principles in the [README](README.md), not a replacement for them. The README explains the *ideas* — positioning, framing, the funnel — as an argument you read straight through; this document is the *lookup* you scan when a term appears and you want its one-line meaning and a programmer's analogy for it. Where a term has a fuller treatment in the README's principles section or in a dedicated document, the entry here gives the short gloss and points you there for the depth. - -A word on why the analogies matter. The fastest way to learn a concept from a discipline you have never studied is to map it onto one you already own, so every entry ties its term to something a systems programmer knows cold — a function signature, a cache miss, a fast path, an API boundary. Treat the analogy as a handhold rather than an equation: it is close enough to make the term stick, and the document it points to carries the precise version. None of this is difficult or secret, as the README stresses; it is a separate discipline with its own words, and this is the word list. - -## The parts of a piece - -Every piece of public writing is built from a small set of named parts, and naming them once is what lets the per-artifact playbooks in [formats.md](formats.md) stay short. These are the anatomy of a landing page, a launch post, or a talk. - -- **Hook** — the opening line whose only job is to make the reader read the next line. It is the fast path of a piece: nearly every reader hits it, most go no further, so if it is slow or unclear nothing downstream ever runs. A good hook carries a concrete benefit or a striking before/after, never the paradigm name. -- **Tag line** — the compressed, one-line description of the whole project, usually the first thing a reader learns about it and sometimes the only thing. It is the project's `--help` summary line: a handful of words that must say what the thing is and why to care. The full treatment, with CGP's candidates weighed, is in [tag-lines.md](tag-lines.md). -- **Subline** — the second line beneath a hook or tag line that carries the honest mechanism the hook left out. The hook earns the click; the subline heads off the reader's first objection — "still Rust, checked at compile time, no runtime cost" — before it can form. -- **Call to action (CTA)** — the single next step the piece asks the reader to take, such as "try the quickstart" or "read the comparison." Offer exactly one, the way a good CLI has one obvious default action: a reader handed five next steps takes none. Matching the CTA to the reader's stage is the job of the conversion ladder in [formats.md](formats.md). -- **Above the fold** — what a reader sees before scrolling, a phrase borrowed from the top half of a folded newspaper's front page. On a README or landing page it is the first screen, and it must carry the tag line, the headline features, and ideally runnable code, because many readers judge from it alone and never scroll. - -## Getting attention and choosing the category - -Marketing supplies the words for how an idea earns a reader's attention and which category the reader files it under, developed in full in the README's [marketing principles](README.md#marketing-positioning-and-attention). These terms recur wherever the section discusses what to lead with. - -- **Positioning** — the deliberate choice of the category a reader files you under, made before they choose a dismissive one for you ("oh, it's a DI framework"). A reader pattern-matches a new tool to the nearest thing they know within seconds, so positioning is picking that match for them rather than leaving it to chance. It is the subject of [positioning.md](positioning.md). -- **Framing** — the choice of angle and wording that decides how a reader reacts to a fact that is true either way. "Overlapping instances made safe" and "clever type-system trickery" can describe the same feature; the frame is the message, not a decoration on it. -- **Value proposition** — the one-sentence answer to "what do I get, and why should I care?", stated as a benefit rather than a feature list. It is the return value of your pitch: if a reader cannot state it after your opening, the call returned nothing. -- **Differentiation** — what CGP does that the obvious alternative cannot, the answer to "why this and not the thing I already use." A reader who sees no difference keeps what they have, so a pitch must name the gap out loud. -- **Elevator pitch** — the thirty-second spoken explanation you could finish before an elevator reaches its floor, the conversational cousin of the tag line. Its constraint is the same: one idea, delivered before attention runs out. - -## Reaching people at scale, and sounding like one project - -Public communication is the craft of being understood by readers you will never meet, and its terms are about clarity and consistency across a whole body of writing rather than any single piece. The README's [public-communication principles](README.md#public-communication-clarity-and-consistency) argue why these invert the habits technical writing trains. - -- **Channel** — where a piece appears: Lobsters, the Rust subreddit, a conference talk, a social thread, the README. Each channel has its own audience, patience, and etiquette, so the channel is chosen before the words are; [attention-and-engagement.md](attention-and-engagement.md) records where CGP's readers actually gather. -- **Curse of knowledge** — the expert's built-in inability to feel what a beginner does not yet know, which makes deep CGP fluency the single biggest risk to explaining CGP well. It is the reason a compiler author writes the error message only they can read. It is the master principle behind nearly every warning in the section, stated in the [README](README.md#principles-from-marketing-public-communication-and-developer-relations) and answered construct by construct in [technical-barriers.md](technical-barriers.md). -- **Message discipline** (or **one voice**) — using the same word for the same idea in every piece, so scattered posts reinforce one another instead of reading as different projects. It is API stability for prose: rename the concept per post and the reader senses incoherence even when they cannot name it. The canonical word list is [vocabulary.md](vocabulary.md). -- **Clarity over completeness** — the rule that one idea a reader keeps beats five accurate ideas they forget, so a pitch says less, more clearly, and defers the nuance. It fights the specification writer's instinct, where completeness is the virtue; in a pitch, completeness is what buries the point. - -## Earning and keeping trust - -Developer relations is communication with an audience that has been marketed to badly its whole career and detects spin on sight, so its terms are about trust — how it is earned, and how one misstep spends it. The README's [developer-relations principles](README.md#developer-relations-trust-and-community) explain why honesty is the strategy rather than a limit on it. - -- **Social proof** — evidence from other people, especially peers and real running systems, that persuades where self-praise cannot. A developer believes another developer's working code far more than any adjective you write; the section argues a flagship adopter is CGP's most valuable missing asset, in [attention-and-engagement.md](attention-and-engagement.md). -- **The funnel** (and **conversion**) — the staged path a reader travels from first hearing of CGP to advocating for it: heard-of, curious, trying, adopting, advocating. It is called a funnel because readers drop out at every stage, so fewer reach each next one, and a **conversion** is one reader taking the step to the next stage. Each stage wants different content and a different [call to action](#the-parts-of-a-piece) — the **conversion ladder** the [formats.md](formats.md) playbooks end on. -- **Show, don't tell** — demonstrate a capability with running code rather than assert it with an adjective, because this audience trusts what it can run and discounts what it is merely told. A before/after on real code outperforms every "flexible" and "powerful" combined. -- **The pile-on** (and **dunking**) — a public group dismissal, where a technical community turns on a post it reads as arrogant or dishonest and piles ridicule on it, often outliving the post. "Dunking" is the act of publicly ridiculing a claim, and a pile-on is the crowd version. The defense is the same as the honest move — claim only what is true, and never disparage another tool to elevate CGP, per the [README](README.md#developer-relations-trust-and-community). - -## When a term is missing - -If you meet a non-technical term in the section that is not defined here, treat that as a gap to close rather than a word to guess at. Add the term to this glossary in the same change and in the same shape — a plain definition and a programmer's anchor — so the next reader is not left guessing, and so this document stays the section's single home for the vocabulary of the craft, the way [attention-and-engagement.md](attention-and-engagement.md) is the single home for its external citations. diff --git a/docs/communication-strategy/key-features.md b/docs/communication-strategy/key-features.md deleted file mode 100644 index 8021efbf..00000000 --- a/docs/communication-strategy/key-features.md +++ /dev/null @@ -1,27 +0,0 @@ -# Key features - -This document sets out the key features CGP should put at the front of its website and README — the few best selling points, distilled to a title and a sentence each. - -## What a key feature is, and how it differs from a selling point - -A key feature is a headline: a title of a few words and a sentence beneath it, scannable in the seconds a visitor gives a landing page before deciding whether to read on. It is not the same artifact as a selling point. [selling-points.md](selling-points.md) is the *full catalog* — every true, advertisable capability, each with the audiences it serves and the phrasings that land — and it is deliberately comprehensive because a writer picks from it per piece and per reader. The key-features list is the opposite: a small, fixed set shown to everyone at once, where the constraint is not coverage but ruthlessness. Only the best few selling points earn a place, because a feature list is read as a whole and its power is inversely proportional to its length. - -Three rules follow from that, and they are the criteria this document applies. **Short**: each feature is a title and one or two sentences, never a paragraph — the elaboration lives in the longer "problems solved" material and the pattern book, not in the headline. **Few**: four to six features, because a list of ten reads as a list of none, and a reader remembers three. **The best only**: each feature must be one of the strongest, broadest, most honest selling points, chosen with the [reader profiles](reader-profiles.md) in mind and worded by the lessons in [tag-lines.md](tag-lines.md) and [skepticism.md](skepticism.md) — which means, above all, that a feature *title* must not lead with a word that repels, and a feature *sentence* must not overclaim to the audience most able to check it. - -## The key features - -The five features below are the headline set, ordered most-important first, each with the title, the sentence to ship, and the reason it earns a slot. Each is chosen to be broad, honest, and free of the words that trigger the reflexes catalogued in [skepticism.md](skepticism.md). - -- **One Interface, Many Implementations** — *"Write many interchangeable implementations of the same interface and choose between them per context, with overlapping and orphan implementations that Rust normally forbids made safe because every choice is explicit and local."* This is CGP's core identity: it names the capability directly and grounds the coherence-freedom advantage (see [bypassing coherence](../concepts/coherence.md)) that the type-system audience prizes. -- **Zero-Cost Abstraction** — *"Everything is resolved at compile time and compiles down to direct calls, so the flexibility costs nothing at runtime and unused providers never reach the binary."* This is the strongest broad reassurance for the Rust audience and the property that sets CGP apart from the runtime tools other languages use; it uses a recognized Rust term in the title while stating the concrete mechanism in the sentence. -- **Type-Safe Wiring** — *"All wiring is checked at compile time, so a missing dependency is a build error rather than a runtime failure — and it runs entirely in safe Rust, with no `dyn`, `Any`, or reflection."* This is the differentiator against both dependency-injection frameworks and dynamic dispatch, and it carries the [`check_components!`](../reference/macros/check_components.md) guarantee that no CGP-specific error can reach runtime. -- **Abstract Over Every Dependency** — *"Write core logic that names its error type, runtime, and I/O abstractly and let each context supply the concrete choice — which keeps the core `no_std`-friendly, from embedded systems and kernels to WebAssembly."* This speaks directly to the systems programmer: it makes the error type, the runtime, and I/O swappable, and its payoff is a core that runs anywhere, down to the most constrained targets. -- **Still Ordinary Rust** — *"CGP is a superset of ordinary traits that you adopt one piece at a time: providers read like normal impls and implicit arguments like normal parameters, so a codebase can use it in one corner and stay otherwise vanilla."* This earns its slot by defusing the biggest adoption fear — that CGP is an all-or-nothing new paradigm — which no other feature addresses and which the [reader profiles](reader-profiles.md) show is the decisive worry for the pragmatist and the evaluator. - -## What the headline set deliberately leaves out - -Two absences are deliberate and worth naming, because each is a tempting addition that would weaken the set. First, there is no feature titled **"Dependency Injection,"** even though CGP is, in substance, compile-time dependency injection: on a general front page the label imports the runtime-framework and "magic" baggage documented in [skepticism.md](skepticism.md) and the [dependency injection](../related-work/dependency-injection.md) comparison, so the DI value is delivered through "Type-Safe Wiring" and "Abstract Over Every Dependency," and the explicit "dependency injection, without the framework" pitch is reserved for the audience-targeted channels where it lands, per [selling-points.md](selling-points.md). Second, CGP's reach into specific domains — **error handling, async runtimes, alternatives to dynamic dispatch, and decomposing large traits** — stays out of the headline set, because those are elaborations of the five features above rather than peers of them; they belong in the longer-form material a reader reaches after the headlines have earned their attention. - -## Phrasing principles for feature titles - -Feature titles obey the same wording discipline as tag lines, compressed to a few rules. Lead a title with the **concrete capability, not an abstraction or a mechanism** — "One Interface, Many Implementations" rather than a title built on "modularity" or "macros." Avoid the words that trigger the documented reflexes: **"modular," "macros," and "magic"** as lead words each cost more attention than they win. Use the **recognized Rust terms** that aid scanning — "zero-cost," "type-safe," "no-std" — where they are accurate, since their familiarity is an asset in a title. And write each feature *sentence* to name a benefit and, wherever a skeptic would balk, the honest qualifier in the same breath — "at compile time," "in safe Rust," "still ordinary Rust" — because the qualifier is what turns a claim the reader would otherwise discount into one they believe. diff --git a/docs/communication-strategy/positioning.md b/docs/communication-strategy/positioning.md deleted file mode 100644 index 0cff315d..00000000 --- a/docs/communication-strategy/positioning.md +++ /dev/null @@ -1,40 +0,0 @@ -# When to use CGP, and when not - -This document is the honest map of where CGP earns its cost and where a plainer tool wins, so a writer can tell a reader when to reach for it and — just as importantly — when not to, because naming the boundary is what makes the recommendation believable. - -## Why the boundary is a marketing asset - -The instinct to sell a tool as universally better is exactly the instinct this audience punishes, so the developer-relations move is to draw CGP's boundary in public rather than let a skeptic draw it for you. The Rust community's default reaction to a new abstraction is "this is over-engineered," and the fastest way to disarm it is to show the author declining to apply CGP everywhere: a reader who watches you say "for this, use a plain trait" believes you about the cases where you say "here, use CGP." This document supplies those lines. It is the positioning companion to [skepticism.md](skepticism.md), which answers "why not just traits" defensively; here the same judgment is offered proactively, as a decision guide a reader can act on. It is a *public positioning* artifact, not a re-teach of the [modularity hierarchy](../concepts/modularity-hierarchy.md) concept — that document is the technical map, and this one is the shorter, honest table a marketing piece or a thread reply reaches for. - -The register throughout is candor, not enthusiasm, because the reader most likely to consult a positioning guide is the [evaluator](reader-profiles.md), whose job is to discount hype. Concede the range where the plainer tool wins in plain words, then state where CGP starts to pay, and let the concession carry the credibility of the recommendation. - -## The rule of thumb - -The one principle that settles most cases is to reach for the lowest rung that expresses the problem, because each step up trades simplicity for modularity a given problem may not need. A capability with a single universal implementation is a function or a blanket trait; a capability that varies by type but never by application is an ordinary trait or an enum; a capability that needs several interchangeable implementations chosen per context, or an implementation for a type you do not own, or overlapping implementations that coherence forbids, is where CGP earns its cost. Said as one line a writer can reuse: **use CGP when a capability needs more than one implementation and the choice belongs to the context — not before.** The full ladder, rung by rung, is the [modularity hierarchy](../concepts/modularity-hierarchy.md); this rule is its compression. - -## The decision guide - -The recurring question is not "is CGP good" but "CGP or this other thing," so the guide below takes the alternatives a reader will actually weigh and, for each, names when the alternative is the right call and when CGP starts to pay. Each entry concedes the alternative's home ground first, because that concession is the point. - -- **A plain trait or generic.** Prefer it when a capability has one implementation, or one per type and a single global choice per type is fine — this is most code, and CGP would be over-engineering. Reach for CGP when the implementations multiply, when the choice must differ per context (mock versus real, per deployment), or when threading a generic parameter through every layer has begun to hurt. The honest framing, per [skepticism.md](skepticism.md), is that CGP is a *superset* of the trait approach, so this is a climb from the plainer tool, not a rejection of it. -- **An enum.** Prefer it for a small, closed, known set of variants with a fixed set of operations — a match is clearer than any machinery. Reach for CGP's [extensible variants](../concepts/extensible-variants.md) when the variant set is open, when independent modules must each contribute a variant or an operation, or when you want the compile-time exhaustiveness of a match without editing every match as the set grows. -- **`dyn Trait` and runtime dispatch.** Prefer it when the set of implementations is not known until runtime — plugins loaded at startup, a heterogeneous collection, a choice made from configuration read at boot — because that runtime openness is exactly what CGP gives up. Reach for CGP when the set of implementations *is* known at build time and you want the decoupling of dynamic dispatch with none of its cost, per [dynamic dispatch](../related-work/dynamic-dispatch.md). This is a genuine either/or, and a piece should say so. -- **A dependency-injection crate.** Prefer a runtime DI container only when you specifically want its runtime lifecycle and object-graph semantics. Reach for CGP when you want compile-time-checked, reflection-free injection with a missing dependency caught at build time — and note that Rust's DI crates are niche and criticized for exactly the limits CGP lifts, such as being unable to declare multiple implementations per component ([attention-and-engagement.md](attention-and-engagement.md), [dependency injection](../related-work/dependency-injection.md)). The care here is to frame CGP as the traits-and-generics approach the reader already prefers, not as a framework, per [skepticism.md](skepticism.md). -- **A generic-programming toolbelt like `frunk`.** Prefer the lighter library when a one-off `HList` or generic-representation manipulation is all a piece of code needs. Reach for CGP's [extensible data](../concepts/extensible-records.md) when the structural machinery is part of a larger component-and-wiring design rather than a standalone transform. Represent the alternative as its own users would, per the section's no-disparagement rule. -- **A hand-rolled macro.** Prefer a small bespoke macro when the code generation is narrow and self-contained. Reach for CGP when you find yourself reinventing its mechanism — the marker-struct-plus-helper-trait workaround for overlapping blanket impls that developers already write by hand ([attention-and-engagement.md](attention-and-engagement.md)) is CGP's core, and doing it once through CGP beats maintaining it per project. -- **Waiting for a language feature.** Prefer waiting when a first-class facility would serve better and you can afford to — Rust's [effects initiative](../related-work/algebraic-effects.md) and its [reflection-and-comptime work](../related-work/reflection.md) are pursuing built-in versions of ground CGP covers. Reach for CGP when you need the capability now, on stable Rust, as a library rather than a nightly feature or an unmerged proposal. The honest note is that CGP is available today and complementary to what the language is building, not a bet against it. - -## Where CGP is simply not the answer - -A few cases are not trade-offs but clear misfits, and saying so plainly is the most trust-building move in any positioning piece. CGP resolves entirely at compile time, so it cannot provide **runtime dynamism** — plugins loaded at startup, monkey-patching, heterogeneous collections, a graph mutated while the program runs — and for those Rust's own `dyn Trait` and the dynamic languages remain the right tools ([dynamic dispatch](../related-work/dynamic-dispatch.md)). A capability with **exactly one implementation** belongs in a plain trait or function; a **small closed variant set** belongs in an enum. And when a program genuinely wants **one instance program-wide** — a single, globally consistent `Ord` for a map key, say — CGP's deliberate per-context choice is the wrong shape, and coherent type classes or a plain trait, which guarantee that global uniqueness, are the better and safer tool ([type classes](../related-work/type-classes.md), [bypassing coherence](../concepts/coherence.md)). Naming this last case is especially disarming to the advanced reader who raised the "coherence exists for a reason" objection, because it shows CGP knows the limit of its own bargain. - -## How to say it - -The wording that makes a positioning claim land follows the section's honesty discipline: concede the alternative's range before asserting CGP's, and prefer a boundary to a superlative. Say it like this: - -- "For one implementation, use a trait. CGP earns its keep when you need several, chosen per context." -- "If your set of implementations is known at compile time, CGP gives you the decoupling of `dyn` with none of the cost. If it isn't, use `dyn`." -- "Rust doesn't need a DI framework, and CGP isn't one — it's the traits-and-generics approach you already use, carried to the cases where doing it by hand stops scaling." -- "Reach for the lowest rung that solves your problem; CGP is a rung you climb to deliberately, not a default." - -Avoid the framings that overreach: do not call CGP "better than traits" (it is a superset you climb to), do not imply it replaces `dyn` (it trades away runtime openness), and do not sell it into a problem with one implementation (that is the over-engineering the reader fears). The concession is not a weakness in the pitch; with this audience it is the pitch, and [skepticism.md](skepticism.md) and [problems-solved.md](problems-solved.md) are where the same boundary is drawn from the objection and the pain sides. diff --git a/docs/communication-strategy/problems-solved.md b/docs/communication-strategy/problems-solved.md deleted file mode 100644 index 7eb48a3f..00000000 --- a/docs/communication-strategy/problems-solved.md +++ /dev/null @@ -1,121 +0,0 @@ -# Problems solved - -This document catalogs the concrete problems CGP removes, written as short before-and-after stories a writer can lead with, so that any piece opens on a pain the reader already feels rather than on the paradigm. - -## How to use these problems - -The whole strategy is problem-first, and this is where the problems live: each entry is a familiar Rust pain, the awkward workaround a developer reaches for today, and the CGP version that removes it, sized down to the smallest honest before/after. This document is the third axis of the section, alongside the capabilities in [selling-points.md](selling-points.md) and the objections in [skepticism.md](skepticism.md) — a selling point says what CGP *can do*, an objection says what a reader *fears*, and a problem says what *hurts now*, which is the one a skeptic will actually follow. Reach for the problem that matches the reader before reaching for any selling point, because "here is the thing you fight every week, gone" persuades where "look what CGP can do" does not. - -Three rules govern how to use an entry. Lead with the **pain, not the mechanism** — open on the workaround the reader recognizes, and let the CGP version arrive as relief rather than as a new thing to learn. Keep the **before/after small and honest** — a five-line diff on ordinary-looking code outperforms a full example, and the "before" must be code the reader would genuinely write, not a strawman that makes CGP look better than it is. And **name the limit in the same breath**, because every entry below is also a place a reader could over-apply CGP, and the honest boundary is what makes the win believable; when the plainer tool is the right one, [positioning.md](positioning.md) says so. - -Each problem names the reader profile it lands with, the selling point and skepticism it maps to, and the honest cost, so a writer can move from a problem straight to the wording that sells it and the objection to defuse. The code shown uses the modern idioms the `/cgp` skill teaches — a provider written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), values read with [`#[implicit]`](../reference/attributes/implicit.md), wiring with [`delegate_components!`](../reference/macros/delegate_components.md) — because a "before/after" that showed dated CGP would teach the reader a dialect they must later unlearn. - -## Mock in tests, run the real thing in production — without `dyn` or a framework - -The most universal pain CGP removes is swapping a real implementation for a fake one across tests and production, which every Rust developer has solved awkwardly. The usual routes each cost something: a `Box` field pays runtime dispatch and infects the type with a trait object; a generic `` parameter threads through every layer that touches it and multiplies as more dependencies join; and a dependency-injection crate brings machinery the Rust community broadly distrusts, as [attention-and-engagement.md](attention-and-engagement.md) records. CGP makes the swap a single wiring line, monomorphized to a direct call. - -The capability becomes a component, and each environment wires the provider it wants: - -```rust -#[cgp_component(EmailSender)] -pub trait CanSendEmail { - fn send_email(&self, to: &str, body: &str); -} - -#[cgp_impl(new SendViaSmtp)] -impl EmailSender { /* connect and send over SMTP */ } - -#[cgp_impl(new RecordEmails)] -impl EmailSender { /* push to a Vec so a test can assert on it */ } - -delegate_components! { App { EmailSenderComponent: SendViaSmtp } } -delegate_components! { TestApp { EmailSenderComponent: RecordEmails } } -``` - -`App` sends real mail and `TestApp` records it; neither pays for `dyn`, the swap is one greppable line, and code that calls `self.send_email(..)` never changes. This is the entry to lead with for the **working developer** ([reader-profiles.md](reader-profiles.md)), it maps to the [swap-implementations selling point](selling-points.md), and it defuses the ["why not just use traits"](skepticism.md) reflex by showing the case where a plain generic would have proliferated. The honest limit: the choice is a line *you* write, not one CGP infers, and for a dependency with exactly one implementation a plain trait is still the right tool. - -## Implement a trait for a type you don't own — no newtype dance - -A pain sharp enough that Rust developers hand-roll CGP's own mechanism to escape it is the orphan rule: you cannot implement a foreign trait for a foreign type, so adding behavior to a type from another crate means wrapping it in a newtype and re-exposing every method you still need — tedious, and it clutters the code with a wrapper the rest of the program must thread around. This is not a theoretical annoyance: there is a repository cataloguing the rule's design problems and a steady stream of posts on the workaround, and one Rust author [independently reinvented CGP's exact marker-struct pattern](attention-and-engagement.md) to get around it, calling the hand-rolled version "3 extra lines to link things together." - -CGP dissolves the orphan rule because a provider implements the *provider* trait for its own zero-sized marker, not the target trait for the foreign type, so coherence never bites — a crate can add a capability to a type it does not own with no wrapper. The mechanics are the subject of [bypassing coherence](../concepts/coherence.md), and the pitch's power is recognition: the reader has written the three-line workaround and would rather not maintain it. This lands with the **working and advanced developer** ([reader-profiles.md](reader-profiles.md)), maps to the [overlapping-and-orphan selling point](selling-points.md), and answers the ["coherence exists for a reason"](skepticism.md) objection by locating CGP's discipline. The honest limit: CGP does not repeal coherence globally; it keeps each choice explicit and per-context, and where a program genuinely wants one instance program-wide, a coherent trait is the better fit. - -## Give one interface many implementations that would otherwise collide - -Closely related but distinct is wanting *several* implementations of one capability at once — the case Rust forbids outright, because two blanket impls that could overlap are rejected even in theory. A developer who wants to serialize a value three ways, or handle an error type by several strategies, hits a wall the language will not let them past without the marker-struct contortion. CGP is built for exactly this: overlapping providers coexist because each is a distinct marker type, and a context selects one explicitly, so the choice is unambiguous locally without global uniqueness. - -The clearest illustration is per-value dispatch, where two applications encode the same type differently, each coherent within itself: - -```rust -delegate_components! { - AppA { - open ValueSerializerComponent; - @ValueSerializerComponent.Vec: SerializeHex, - } -} - -delegate_components! { - AppB { - open ValueSerializerComponent; - @ValueSerializerComponent.Vec: SerializeBase64, - } -} -``` - -`AppA` serializes bytes as hex and `AppB` as base64, and the overlapping providers never conflict because each context names one. This is the standout for the **type-system and functional-programming reader** ([reader-profiles.md](reader-profiles.md)), it maps to the [overlapping-and-orphan selling point](selling-points.md) and its "type classes without the orphan rule" one-liner, and it is the case [type classes](../related-work/type-classes.md) frames in full. The honest limit: this is genuinely more machinery than a single trait needs, so it earns its place only when the several implementations are real — the boundary [positioning.md](positioning.md) draws. - -## Swap your error type or runtime by changing one line - -A quieter pain, felt hardest in libraries and reusable cores, is that fallible code commits to a concrete error type early and then cannot easily change it, so a decision to move from `anyhow::Error` to a custom enum, or from one async runtime to another, ripples through every signature. CGP makes the error type — or the runtime, or any cross-cutting type — abstract, chosen by the same wiring that selects behavior, so the concrete choice lives in one place and generic code never names it. - -Code written against an abstract [`HasErrorType`](../reference/components/has_error_type.md) works unchanged whichever error a context wires: - -```rust -delegate_components! { - App { - ErrorTypeProviderComponent: UseType, - } -} -``` - -Switching that line to a custom `AppError` retargets every fallible provider in the context, touching no logic. This resonates with the **ML-module and systems reader** ([reader-profiles.md](reader-profiles.md)) and underpins CGP's [modular error handling](../concepts/modular-error-handling.md); it maps to the [abstract-types selling point](selling-points.md) and attaches to the live `anyhow`-versus-`thiserror` conversation ([attention-and-engagement.md](attention-and-engagement.md)). The honest limit: CGP defers and configures the type, it does not seal a representation the way an ML module does — hiding a representation is still Rust's module privacy, and the wiring keys live under `cgp::core::error`, not the prelude, so a piece that shows this should import them. - -## Break up a trait that grew into a monolith - -A pain that arrives with a codebase's age rather than its domain is the trait that accreted responsibilities until every implementor must supply the whole surface and every change touches all of them — the "god trait" that resists decomposition because splitting it breaks impls and threads new generic parameters everywhere. CGP lets one large capability become a set of independently wired components, each with its own providers, so an implementor supplies only what it uses and a context assembles the pieces without a single monolithic impl. - -Because each component is wired separately, a context can compose a behavior from many small providers and reuse each across contexts, and adding a capability is adding a wiring line rather than editing a shared trait every type implements. This is the decoupling pitch for the **framework author and the evaluator weighing maintainability** ([reader-profiles.md](reader-profiles.md)); it maps to the [gradual-adoption selling point](selling-points.md) and the decoupling benefits, and it rests on the [consumer/provider split](../concepts/consumer-and-provider-traits.md). The honest limit: decomposition is a judgment call, and a small, stable trait with one implementation should stay a plain trait — the [modularity hierarchy](../concepts/modularity-hierarchy.md) is the map of when the split pays, and [positioning.md](positioning.md) draws the line for a marketing piece. - -## Write a framework over any type's structure — without runtime reflection - -For the author of a serialization library, a builder, an ORM, or a configuration system, the recurring pain is code that must work over the *shape* of a user's type without a general reflection facility Rust lacks, which pushes them toward per-type derive macros, stringly-typed field access, or a reflection crate that walks a descriptor at runtime. CGP encodes a type's fields and variants as type-level data the trait system resolves against, so a framework written once recurses over any type that opts in with a derive, fully checked when written and with nothing walked on each call. - -A type opts in with a derive, its structure becomes a type-level list, and generic code processes it with static checking and no runtime introspection — the payoff of reflection without its cost, developed in [extensible records](../concepts/extensible-records.md) and [extensible variants](../concepts/extensible-variants.md). This is the entry for the **framework and tooling author** ([reader-profiles.md](reader-profiles.md)), it maps to the [generic-over-structure selling point](selling-points.md), and it sits in the live reflection-and-comptime conversation ([attention-and-engagement.md](attention-and-engagement.md), [reflection](../related-work/reflection.md)). The honest limit, stated in the same breath: it works only on types that derive the shape and does no runtime introspection, so a reader must not be sold "reflection for Rust" and then look for an API to query a type — the precise frame is "compile-time structural reflection encoded in types." - -## Keep a provider's dependencies out of your public API - -A subtler pain, felt by library authors, is that trait-based dependency injection leaks: because any type named in a public trait's method signature must itself be public, exposing a dependency through a generic trait forces internal types into the public API and breaks encapsulation, a cost [documented with its source](attention-and-engagement.md) in the Rust community. CGP keeps a provider's dependencies as impl-side constraints — declared where the implementation lives, through [`#[uses]`](../reference/attributes/uses.md) and [`#[implicit]`](../reference/attributes/implicit.md), rather than in the consumer trait's signature — so the public interface stays clean while the requirements are still explicit and compiler-checked. - -A caller who bounds on the consumer trait sees only the capability, never the dependency types the provider happens to need, so a library can require an internal helper without publishing it. This is a targeted pitch for the **library author** and complements the [explicit-and-compiler-checked-dependencies selling point](selling-points.md), which now carries the encapsulation angle; it rests on [impl-side dependencies](../concepts/impl-side-dependencies.md). The honest limit: this is a narrower, more advanced benefit than the others here, so it belongs in a piece aimed at library authors rather than in a general hook, where it would land as abstract. - -## Read a CGP compile error without decoding a wall of generated types - -The pain that has done CGP the most adoption damage lives not in the language but in its errors: a small wiring mistake — a context missing one field a provider needs — expands, through the generated code, into screens of diagnostics naming `IsProviderFor`, `CanUseComponent`, and a nested `Symbol<…>` spine the programmer never wrote, with the real cause buried in the machinery or, in the worst class, suppressed entirely. A developer who meets that wall on their first mis-wire often concludes CGP is unusable and leaves, which is why this is the pain most worth showing removed. - -The "before" is a `Rectangle` context wired to an area calculator whose provider reads a `height` field the struct does not have. Under plain `cargo check`, the failure surfaces as an `E0277`/`E0599` cascade that names the consumer and provider traits but not the missing field — the [hidden unsatisfied-dependency class](../errors/hidden/unsatisfied-dependency.md), where the default trait solver omits the cause rather than merely burying it. The "after" is the same mistake through CGP's error toolchain: - -```text -$ cargo cgp check -error[E0277]: [CGP-E001] the consumer trait `CanCalculateArea` is not implemented for context `Rectangle` - = note: root cause: [CGP-E106] missing field `height` on `Rectangle` - this is required through the dependency chain: - [CGP-E101] consumer trait impl `CanCalculateArea` for context `Rectangle` - └─ [CGP-E102] provider trait impl `AreaCalculator` with context `Rectangle` for provider `RectangleArea` - └─ [CGP-E106] missing field `height` on `Rectangle` -``` - -[`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) turns on Rust's next-generation trait solver to un-hide the suppressed cause, then leads with it — naming the missing `height` field and drawing the dependency chain that reaches it as a `cargo tree`-style tree, each line tagged with a `[CGP-Exxx]` code. This maps to the [tooling selling point](selling-points.md) and answers the ["wall of generated types"](skepticism.md) objection head-on, for every reader but most sharply the **working developer and the evaluator** ([reader-profiles.md](reader-profiles.md)) weighing whether a team can live with the diagnostics. The honest limit, stated in the same breath: `cargo-cgp` is v0.1.0-alpha and reshapes the classes it recognizes — the core wiring errors — while some, orphan-rule errors among them, still pass through as the compiler wrote them, so the honest claim is that the error experience is dramatically better and improving, not that it is solved. The reference [`cargo-cgp.md`](../reference/cargo-cgp.md) and the [error catalog](../errors/README.md) carry the fuller picture. - -## Choosing which problem to lead with - -The last decision is which of these to open on, and it is settled by the reader, not by which problem is most impressive. Name the one or two dominant profiles for the piece and its channel first, per [reader-profiles.md](reader-profiles.md), then pick the problem that reader feels most sharply: the mock-in-tests swap for the pragmatic majority, the orphan-rule escape for the trait-heavy developer, overlapping implementations for the type-system reader, the error-type swap for the systems and ML-module reader, the monolith decomposition for the evaluator, and the structure-generic framework for the tooling author — and the readable-errors story for the evaluator or any reader who has heard CGP's diagnostics are unusable. When a piece must serve a broad public audience, lead with the mock-in-tests swap or the orphan-rule escape, because they are the most widely felt and the least likely to read as astronaut architecture — and hold the more advanced problems for the channels where their reader gathers. diff --git a/docs/communication-strategy/reader-profiles.md b/docs/communication-strategy/reader-profiles.md deleted file mode 100644 index 50c2ce2c..00000000 --- a/docs/communication-strategy/reader-profiles.md +++ /dev/null @@ -1,87 +0,0 @@ -# Reader profiles - -This document profiles the kinds of readers that public-facing CGP writing must serve, so a writer can name who a piece is for and shape it to what that reader already knows, hopes for, and fears. - -## Why reader profiles matter - -Public writing about CGP lands only when it is written for a definite reader, because CGP's ideas are unfamiliar enough that one framing helps an audience while the same framing loses another. "Overlapping instances made safe" reads as a gift to a Haskeller and as a warning to a pragmatic Rust engineer that the crate is too clever. "No runtime container" reassures a systems programmer and confuses a Spring developer who cannot picture dependency injection without one. Naming the reader is therefore the first decision in any piece, ahead of the outline and the examples, because it decides which benefit to lead with, how deep to go, which analogies to reach for, and which misunderstanding to head off first. - -Two axes describe most readers, and they vary independently. The first is **how much Rust the reader knows**, from someone who learned the language a few months ago to someone fluent in higher-ranked trait bounds and type-level programming. The second is the **disposition** the reader brings: an enthusiast who is delighted by expressive, type-level machinery, against a pragmatist who is wary of complexity and distrusts "clever" code on sight. The axes are orthogonal — an expert can be either an enthusiast or a hardened skeptic — and in the Rust community the skeptic is both the more common and the harder reader to win, so writing that satisfies the skeptic tends to satisfy everyone. A softer third axis is the **mental model a reader imports** from another language or ecosystem, which decides which analogies land and which mislead; those backgrounds are covered in depth by the [related-work](../related-work/README.md) documents and summarized as reader traits below. - -Real readers are blends, so treat these profiles as lenses to combine rather than boxes to sort into. A single reader might be an advanced Rust developer, a former Haskeller, and a pragmatic skeptic all at once, and a piece aimed at them should draw on all three. For each profile below, the goal is to hold five things in view: who the reader is, what they already know, what excites them and so makes the hook, what makes them skeptical and so must be defused, and how a piece should be written to reach them. - -## Readers by Rust experience - -The clearest way to place a reader is by how much Rust they command, because that sets the ceiling on how much CGP machinery a piece can show before it stops teaching and starts alienating. The three profiles here span the range the writing must cover, and the enthusiast-versus-skeptic disposition cuts across all of them. - -### The Rust newcomer - -The newcomer learned Rust recently and is still absorbing ownership, borrowing, and the basics of traits, so most of CGP's machinery sits far over their current horizon. They are comfortable with structs, enums, and calling methods, they have met common traits like `Clone` and `Debug`, and they may still be fighting the borrow checker; generics-heavy or type-level code is not yet part of their vocabulary. What excites this reader is the promise that they do not have to master a new paradigm to benefit — that CGP code can *read* like ordinary functions and plain trait impls, that an `#[implicit]` argument looks like a normal parameter, and that they can adopt one small piece at a time. - -The risk with the newcomer is that the full paradigm buries them: the consumer/provider split, wiring tables, and any mention of coherence will overwhelm someone still forming their model of traits, and CGP's long generated-type error messages are demoralizing to a reader who cannot yet parse them. Write for this reader by leading with the gentlest on-ramp — a single capability defined as a function, a context that gains it with one wiring line — and keep the internals (`IsProviderFor`, `DelegateComponent`, the generated blanket impls) entirely out of sight. Reach for familiar analogies, such as a wiring table as a settings map, never open with the coherence problem, and say explicitly that the whole system need not be understood at once. The honest message is that CGP meets them where they are and grows with them. - -### The working Rust developer - -The working developer is fluent in traits, generics, and everyday patterns, ships production Rust, and judges any new tool by whether it earns the complexity it adds — this is the pragmatic majority of the Rust audience and the one most writing should target. They know trait objects, the common ecosystem crates, and the builder and newtype patterns; they are comfortable reading generic code but actively value simplicity and are allergic to over-abstraction. What excites this reader is a concrete, familiar pain that CGP removes: swapping a real implementation for a mock across tests and production without paying for `dyn` dispatch, escaping the orphan-rule newtype dance, or decoupling modules without pulling in a dependency-injection framework — shown on code that looks like code they already write. - -The working developer's skepticism is the decisive obstacle, because they have watched "enterprise" abstraction ruin codebases and will pattern-match CGP to that at the first sign of ceremony. Their questions are whether it is over-engineered, what it does to compile times, how one debugs generated code, how long a teammate takes to learn it, and whether it is a lock-in risk. Win this reader by leading with a problem rather than the paradigm, showing a before/after on realistic code, and being candid about cost and about when *not* to reach for CGP. On the debugging and error-message worry specifically, point this reader at [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's error toolchain, which reshapes CGP's compiler errors to lead with the root cause — conceding in the same breath that it is a v0.1.0-alpha that handles the core wiring errors rather than every class. Emphasize that wiring is resolved at compile time and erased — there is no runtime cost — and demonstrate the vanilla-looking idioms so the code does not read as alien machinery. Restraint earns this reader's trust; a single overclaim loses them, often permanently and loudly. - -### The advanced Rust developer - -The advanced developer commands generics, associated types, higher-ranked trait bounds, `PhantomData`, and the coherence and orphan rules, and reads CGP fluently as trait machinery — often with delight, sometimes with a critic's eye. They may already push Rust's type system in their own crates, so nothing about a blanket impl or a type-level table is foreign, and they can follow an expansion without hand-holding. The enthusiast among them is excited by the expressive power: many overlapping impls made legal at once, provider selection made per-context, structure encoded as type-level lists, and the fact that all of it is zero-cost and works on stable Rust. This reader can become CGP's strongest advocate, and they want the real mechanism, not a simplified picture. - -The critic among the advanced readers is skeptical for informed reasons, and those reasons deserve engagement rather than deflection: the opacity of macro-generated code against hand-written traits, the compile-time and type-checking cost, the quality of the error messages, whether the abstraction pays for itself over plain trait bounds, and the principled worry that coherence exists for a reason. Write for this reader by showing the machinery honestly — the desugaring, the [`DelegateComponent`](../reference/traits/delegate_component.md)/[`IsProviderFor`](../reference/traits/is_provider_for.md) mechanism, and the [coherence trade-off](../concepts/coherence.md) argued on its merits — and by treating the [comparison to type classes](../related-work/type-classes.md) as a real intellectual exchange. Do not dumb it down, but do not mistake fluency for approval: this reader still asks whether the complexity buys genuine modularity or mere novelty, and because they shape opinion in the community, honesty and depth matter more here than anywhere else. - -## Readers by prior mental model - -Many readers arrive already fluent in a paradigm that CGP resembles, and the fastest way to reach them is through vocabulary they already own. Each profile below names the disposition that background tends to bring and points to the [related-work](../related-work/README.md) document that carries the full, cited comparison; read here for who the reader is, and read there for the depth of the mapping. - -### The functional-programming and type-system practitioner - -This reader comes from Haskell, Scala, OCaml, PureScript, F#, or Lean, and holds type classes, implicit parameters, row types, effect handlers, and ML modules as native concepts. They are the audience most predisposed to become advocates, because CGP hands them things their own languages forbid or make fragile — overlapping and orphan instances made safe, per-context selection, and extensible records and variants in a systems language — and they often actively want more type-level power, not less. The way in is to map the vocabulary directly (a component is a class or signature, a provider is a first-class instance or structure, wiring is instance resolution made explicit, an impl-side dependency is a class constraint or an effect) and to lead with the pains coherence and the orphan rule cause them. - -The caution with this reader is that they are precise about their own terms and will notice a loose analogy immediately, and some will ask why not simply stay in the functional language whose ergonomics they prefer. Answer that honestly — CGP's value is bringing the idea to Rust's performance and ecosystem, not out-competing the source language on elegance. The full treatments live in [type classes](../related-work/type-classes.md), [implicit parameters](../related-work/implicit-parameters.md), [row polymorphism and extensible data](../related-work/row-polymorphism.md), [algebraic effects and handlers](../related-work/algebraic-effects.md), and [ML modules and modular implicits](../related-work/ml-modules.md). - -### The enterprise and dependency-injection developer - -This reader comes from Java, Kotlin, or C# and the Spring, Guice, or Dagger world, and thinks in interfaces and implementations, inversion of control, testability, and wiring an object graph. What excites them is compile-time-checked, reflection-free dependency injection with per-context choice — "Dagger taken further" — where dependencies are explicit, nothing unused reaches the binary, and a missing binding is a compile error rather than a startup exception. The vocabulary maps cleanly: a provider is a bean or binding, `delegate_components!` is the container configuration, an impl-side dependency is a constructor parameter, and [`check_components!`](../reference/macros/check_components.md) is the graph validation a container runs — the difference being when it runs. - -The one assumption to defuse immediately is the runtime container: this reader will imagine an object somewhere holding the graph and resolving it by reflection, and so will expect a runtime cost and a runtime failure mode that CGP does not have. Say plainly that CGP's container is the type system and its graph is a set of trait impls resolved during compilation. Lead with the hidden-dependency and `NullPointerException` complaints that drove their own community toward constructor injection, since CGP answers exactly those. The full treatment is in [dependency injection](../related-work/dependency-injection.md), with the object-model and vtable framing in [dynamic dispatch](../related-work/dynamic-dispatch.md). - -### The dynamic-language developer - -This reader comes from Python, Ruby, or JavaScript and values duck typing, flexibility, and rapid iteration. What excites them is that CGP provider code reads like duck-typed code — sending messages to a context it never names — while being checked so it cannot blow up at runtime: the decoupling of dynamic dispatch with none of its cost, and duck typing that cannot throw `NoMethodError`. The framing that resonates is that CGP is the mechanisms they already use, with the runtime taken out. - -The skepticism here is aimed at Rust itself as much as at CGP: this reader fears rigidity and verbosity, cares more about not fighting the compiler than about zero-cost, and can experience CGP's long compile-time errors as exactly the rigidity they came to a dynamic language to escape. Be honest that runtime malleability — plugins loaded at startup, heterogeneous collections, monkey-patching — is not what CGP is for, and frame its "late binding" as late to the *wiring site* rather than to runtime. The full treatment is in [dynamic dispatch](../related-work/dynamic-dispatch.md). - -### The framework, library, and tooling author - -This reader builds serialization, ORMs, configuration systems, editors, or test frameworks, and thinks about generic-over-structure code, compile times, binary size, and the derive macros and reflection those tools lean on. What excites them is reaching reflection's payoff — write the framework once and have it work over any user type — with none of reflection's runtime cost or stringly-typed failure, because CGP encodes a type's shape as type-level lists the trait system resolves against, checked when the code is written and able to recurse into each field's own type. - -The objections this reader raises are compile-time cost and error quality, that it works only on types that opted in by deriving CGP's machinery, and that it is not a runtime introspection facility. Meet them by not calling CGP "a reflection system" — a reader sold on that will look for an API to query and find trait bounds instead — and by framing it as compile-time structural reflection encoded as types, with the opt-in derive requirement and the absence of runtime introspection stated up front. The full treatment, including the comparison to Bevy, facet, and Rust's emerging compile-time reflection, is in [reflection](../related-work/reflection.md). Reflection is also a live, high-attention area right now — first-class compile-time reflection is an officially pursued Rust project goal with an early implementation already landed — which makes the topic timely but sharpens the honest framing: present CGP as available today and type-level, complementary to the built-in facility the language is building rather than a rival it will absorb, and see [attention-and-engagement.md](attention-and-engagement.md) for where this conversation is drawing attention. - -## Readers by role and point of contact - -Beyond expertise and background, a reader's role and where they first meet CGP decide how much attention they will give and what decision they are making. These two profiles matter disproportionately for the twin goals of public writing — gaining attention and minimizing misunderstanding — because they govern first impressions and adoption. - -### The first-contact skimmer - -This reader is scrolling a social feed, a link aggregator, or a chat channel, gives the piece a few seconds, and will form a snap judgment they may broadcast. What earns their attention is a sharp, concrete hook — a one-line value proposition or a striking before/after — that sounds novel and real at once. What loses them is anything that lets them pattern-match CGP to a category they already dismiss: "just another DI framework," "macro magic," "over-abstracted Rust," "just use traits." One phrase that reads as pretentious or hand-wavy and they bounce, or worse, dunk. - -Write for the skimmer by leading with the single clearest benefit on concrete code, preempting the obvious dismissals in the opening lines, and keeping jargon out of the first impression. Because this reader shapes CGP's public reputation out of all proportion to the time they spend, the whole strategy of gaining attention while minimizing misunderstanding lives or dies in the first few lines they see. - -### The evaluator and decision-maker - -This reader is a tech lead, staff engineer, or architect weighing whether to adopt CGP for a team or a product, and they read for risk as much as for benefit. The benefits that move them are maintainability, testability, decoupling, performance guarantees, and reduced boilerplate at scale, backed by evidence that it works in real systems. Their skepticism is about cost and risk they will own: team ramp-up and hiring, the maturity of a young paradigm and ecosystem, how the code is debugged, and the wisdom of betting a codebase on something novel. - -Reach this reader with candor rather than enthusiasm: be honest about maturity and cost, show that CGP is a superset of ordinary traits so it can be adopted incrementally and stepped back from, and state plainly where it fits and where it does not. When the debugging worry surfaces, point to [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) as CGP's deliberate answer to unreadable errors, and model the candor this reader rewards by conceding it is itself an early pre-release (v0.1.0-alpha) that reshapes the core wiring errors rather than every class. Address the "can my team learn this" question head-on rather than letting it fester. Overselling is fatal here, because this reader's job is to discount hype. - -## What nearly every reader shares - -Across all of these profiles, a few things hold for almost every reader, and they are the highest-leverage points for any public piece. The universal question underneath every profile is *is this worth the complexity?* — and the only convincing answer is a concrete problem solved plus honest limits, never enthusiasm alone. Three misunderstandings recur across nearly every audience and should be preempted early: that CGP has runtime cost or a runtime container and graph, when its wiring is resolved at compile time and erased; that it is "just another dependency-injection framework" or "just macro magic," when it is a trait-level paradigm with static guarantees; and that using it requires understanding all of its internals, when its vanilla-looking idioms let it be adopted a little at a time. Naming and defusing these three is often the single most valuable move a piece can make. - -The default reaction to CGP is the reflex to pattern-match it to something already familiar and dismissible, so the writer's real job is to make the genuinely novel part legible before that reflex fires. A tone that carries the whole spectrum is concrete over abstract, problem-first, honest about trade-offs, and free of hype — and when a piece must serve a mixed public audience, it should default to satisfying the pragmatic skeptic and the first-contact skimmer, because they are the least forgiving and the most numerous, and writing that wins them rarely loses anyone else. - -## Using these profiles - -To put this document to work, name the one or two dominant profiles for the piece and its channel before writing anything else. Draw the opening hook from what excites that reader and the first objection to defuse from what makes them skeptical, and pull the vocabulary and analogies from the matching [related-work](../related-work/README.md) document so the comparison speaks the reader's own language. When the target is a broad public audience rather than a specific community, write to the skeptic and the skimmer first and let the enthusiast, who forgives more, come along. diff --git a/docs/communication-strategy/selling-points.md b/docs/communication-strategy/selling-points.md deleted file mode 100644 index b7361665..00000000 --- a/docs/communication-strategy/selling-points.md +++ /dev/null @@ -1,149 +0,0 @@ -# Selling points - -This document catalogs the selling points CGP should advertise and, for each, the phrasings that make it land and the phrasings that backfire, so a writer can pick the right pitch for the right reader and word it in a way this audience will believe. - -## How to use these selling points - -A selling point is a true CGP capability, stated in the reader's own language, that answers a problem the reader already feels. That definition carries three obligations at once, and dropping any one turns a selling point into the kind of claim the Rust audience punishes. It must be **true**, because the readers most worth winning are the ones most able to catch an overclaim; it must be **framed for a reader**, because the same capability excites one audience and alarms another, as [reader-profiles.md](reader-profiles.md) lays out; and it must be **anchored to a pain**, because "look what CGP can do" persuades no one, while "here is the problem you have, gone" persuades the skeptic. Lead every piece from a concrete problem, reach for the selling point that removes it, and state it in the words below. - -Two habits make the difference between a pitch that lands and one that invites a pile-on. First, pair the advantage with its cost whenever the audience is a skeptic — the [skepticism](skepticism.md) document is the companion to this one, and a selling point delivered without acknowledging its matching objection reads as spin. Second, prefer the concrete over the grand: a one-line before/after on familiar code outperforms any adjective, and "there is no runtime cost" outperforms "blazingly fast." The phrasing guidance in each section below is not decoration; the wrong word on the right capability is how CGP gets pattern-matched to something the reader already dismisses. - -## The headline pitch - -CGP's one-line identity should say what it *is* and what it *costs* in a single breath, because the reader's first question is "what is this" and their immediate second is "what does it cost me." The value proposition that does both is that **CGP is a language extension for Rust, with pluggable trait implementations at compile-time: write many interchangeable implementations of an interface and choose between them per context, resolved entirely at compile time with zero runtime cost.** That sentence leads with the benefit (pluggable implementations, chosen per context), names the mechanism honestly (an extension that compiles to ordinary Rust, not a runtime), and closes the escape hatch a skeptic reaches for (no runtime cost). Shorter openers — "swappable implementations, wired per context, erased before it runs" — keep the same three beats. - -Resist the urge to lead with the machinery. Opening on the consumer/provider trait split, `DelegateComponent`, or coherence-bypassing loses every reader but the advanced enthusiast, and even they respond better to a problem first. The internals are what a curious reader graduates *into*, not what they should meet in the first sentence. Reserve the phrase "context-generic programming" for after the value has landed; as an opener it is a name the reader cannot yet decode, and a name is not a pitch. - -## Zero runtime cost - -The strongest broad selling point is that all of CGP's flexibility is resolved at compile time and erased before the program runs, so the modularity costs nothing where it matters. There is no container holding a graph, no reflection walking types, no vtable, and no dynamic dispatch; a wired call monomorphizes to a direct function call, and a provider a context does not use is not in the binary. This is the selling point that answers the working developer's first suspicion — that abstraction means overhead — and the one that separates CGP from the runtime dependency-injection and reflection tools other languages rely on, as the [dependency injection](../related-work/dependency-injection.md) and [reflection](../related-work/reflection.md) comparisons detail. - -Say it like this: - -- "Resolved at compile time and compiled away — a wired call is a direct call." -- "No runtime container, no reflection, no vtable; nothing about the wiring survives into the running program." -- "Dependency injection that costs nothing at runtime." -- "Unused providers are not in your binary." - -Avoid the phrasings that overreach or read as slogans. Skip "blazingly fast" and any comparative speed claim without a benchmark to cite; the honest and stronger claim is that there is no cost to compare, not that CGP wins a race. "Zero-cost abstraction" is accurate but worn, so prefer the concrete "compiled to a direct call." And never imply there is a runtime component "but a fast one" — the point is that there is none, and blurring that invites the exact runtime-cost objection the selling point exists to remove. - -## Swap implementations without the runtime penalty - -CGP's central capability is that one interface can have many interchangeable implementations, chosen per context — per deployment, per test, per environment — which is the everyday problem that drives developers to trait objects or dependency-injection frameworks in the first place. A context wires a production implementation; a test wires a mock; a second application wires a third choice; none of them pay for `dyn` dispatch, and none of them collide. For the working developer this is the most legible benefit, because the mock-in-tests, real-in-production split is a problem they have solved awkwardly before, and CGP solves it with a swap of one wiring line. - -Say it like this: - -- "One interface, many implementations — the context picks which one, and the choice is a line you can read." -- "Mock it in tests, run the real thing in production, with no trait objects and no runtime dispatch." -- "The same component resolves to different implementations in different contexts, with no conflict between them." - -Avoid implying the choice is automatic. CGP does not find the implementation for you — you name it in a wiring table — and a pitch that says "CGP picks the right one" sets up the reader to feel misled the first time they write a `delegate_components!` entry. Frame the explicitness as the feature it is: the choice is one greppable place, not a resolution search you have to reverse-engineer. This is the wording that also defuses the implicit-resolution skepticism covered in [skepticism](skepticism.md). - -## Dependencies that are explicit and compiler-checked - -CGP makes what a provider needs from its context explicit in its declarations and enforces it at compile time, which answers the loudest complaint against dependency-injection frameworks directly. A provider states its dependencies through `#[uses]` and `#[implicit]` rather than hiding them, so a reader sees what a component requires without spelunking, and a context that fails to satisfy a dependency is a compile error at the wiring site — not a startup exception or a `NullPointerException` in production. This is "the explicitness the Spring community learned to prefer, by default," and it lands hardest with the enterprise and dependency-injection reader profiled in [reader-profiles.md](reader-profiles.md). It also answers a pain the pure-Rust reader feels without any framework at all: because a provider's dependencies live in its own implementation rather than in a public trait's method signature, they never force internal types to become public — the encapsulation leak that hand-rolled trait-based dependency injection is prone to, documented with its source in [attention-and-engagement.md](attention-and-engagement.md). - -Say it like this: - -- "A provider's dependencies are stated where the implementation lives and checked by the compiler." -- "A missing dependency is a compile error at the wiring site, not a runtime surprise." -- "No hidden dependencies: what a component needs is in its signature, not buried in its body." -- "[`check_components!`](../reference/macros/check_components.md) is your container's startup validation — run at compile time instead of at boot." -- "Dependencies stay in the implementation, not the public interface, so they never leak internal types into your API the way a public generic trait does." - -Avoid overselling the verification as effortless. The check is something you write, and its error messages, though they name the missing dependency, are verbose — say so when the audience is technical, because pretending the diagnostics are pretty is exactly the kind of small dishonesty that costs trust. Frame the trade honestly: you write a check line, and in return the whole class of runtime wiring failures cannot occur. The verbosity itself now has a dedicated answer in the tooling — the next selling point — but keep telling a technical reader that the raw compiler output is a wall of generated types, because that is what they meet without the tool. - -## First-class tooling makes CGP's errors readable - -A selling point CGP could not honestly make until recently is that its compiler errors are now served by a dedicated checker that leads with the root cause, so the single biggest thing that has ever driven readers away from CGP is being attacked directly. The tool is [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's error toolchain: a cargo subcommand, `cargo cgp check`, that stands in for `cargo check` and rewrites CGP's compiler errors into a compact, root-cause-first form — much as Clippy layers its own analysis on top of `rustc`. Two mechanisms carry the pitch. It turns on Rust's next-generation trait solver, which **un-hides** the dependency errors the default solver suppresses — the worst class of CGP error, where plain `cargo check` says only that a method's bounds are unsatisfied and never names the missing field, now names it — and it **rewrites the classes it recognizes** to lead with the real cause, tagging each with a `[CGP-Exxx]` code and drawing the dependency chain as a `cargo tree`-style tree. This is the selling point that finally answers ["the error messages are a wall of generated types"](skepticism.md), the objection most likely to bite a real user. - -The honesty this selling point demands is unusually load-bearing, because the reader can `cargo install` the tool and check the claim within the hour. `cargo-cgp` is **v0.1.0-alpha**, a young pre-release: it already handles the core wiring-error classes well — missing field, missing wiring, unmet dependency chains, wrong field type, duplicate wiring — but it does *not* yet reshape every class, and some (orphan-rule errors among them) still pass through as the compiler wrote them. So the frame to hold is "the error experience is dramatically better and actively improving," never "solved." Conceding the alpha is what makes the claim believable to this audience, not a hedge that weakens it; the reference [`cargo-cgp.md`](../reference/cargo-cgp.md) and the [error catalog](../errors/README.md) carry the class-by-class detail. - -Say it like this: - -- "CGP's errors are served by a dedicated checker that leads with the root cause — `cargo cgp check` names the missing field instead of a wall of generated types." -- "The next-generation solver un-hides the cause the default compiler buries; the tool then leads with it and tags it with a code." -- "A young but fast-improving toolchain — v0.1.0-alpha — already reshapes the core wiring errors." - -Avoid the overclaim the tool's newness makes tempting. Do not say "CGP errors are now as clear as any other Rust error," and do not call the diagnostics "solved" or "fixed" — the tool reshapes the *recognized* classes and concedes the rest, so a reader who hits an unreshaped class after reading "solved" discounts everything else you said. Do not imply cargo-cgp changes what CGP does: the errors were always shaped by CGP's real design — lazy wiring, generated types — and the tool makes them *readable*, it does not remove them from the language. And never state the capability without the alpha concession nearby, because with this audience the concession is the credibility. - -## It reads like ordinary Rust, and you adopt it gradually - -A decisive selling point for the cautious majority is that CGP code can read like ordinary functions and plain trait impls, and that it is a superset of normal traits you can adopt one piece at a time. An `#[implicit]` argument looks like a function parameter; a provider written with `#[cgp_impl]` looks like a trait impl; a consumer trait can be implemented directly on a type with no CGP machinery at all. Nothing forces a project to swallow the whole paradigm at once, and a codebase can use a single component in one corner and stay otherwise vanilla. This is the selling point that disarms the "too clever / all-or-nothing" fear the Rust community brings to any new abstraction. - -Say it like this: - -- "It's a superset of ordinary traits — start with one component and leave the rest of your code unchanged." -- "Providers read like normal impls; implicit arguments read like normal parameters." -- "Adopt it gradually; you never have to buy the whole paradigm to use part of it." - -Avoid two opposite mistakes. Do not claim "no boilerplate" — there is wiring, and the honest framing is that CGP *moves* boilerplate into one readable place, not that it erases it. And do not undersell by leading with the machinery that makes the gradual on-ramp possible; the reader wants to hear "it looks like the Rust you already write," not a tour of the desugaring that achieves it. - -## Overlapping and orphan implementations, made safe - -For the type-system and functional-programming audience, the standout selling point is that CGP makes legal the overlapping and orphan implementations their languages forbid or make fragile — and makes them safe by keeping every choice explicit and local. Because a provider implements a provider trait for its own marker type rather than for the context, the orphan rule and the overlap rule do not bite: a crate can define many implementations of one capability, and implement a capability for a type it does not own, without the newtype dance or the coherence contortions. The per-context wiring table is what keeps this safe rather than chaotic, so incoherence never means the indeterminism that makes Haskell's `INCOHERENT` a footgun. The full comparison lives in [type classes](../related-work/type-classes.md) and [bypassing coherence](../concepts/coherence.md). - -This selling point has a rare asset behind it: developers already reinvent CGP's mechanism by hand. Rust programmers who hit the "no two blanket impls may overlap" wall reach independently for the same zero-sized-marker-plus-helper-trait pattern CGP is built on, as [attention-and-engagement.md](attention-and-engagement.md) documents — so the pitch reminds a reader of a workaround they have written and would rather not maintain, not a capability they must be talked into wanting. Leading with that recognition ("you have written the three-line version of this") disarms the "over-engineered" reflex faster than any claim about expressiveness. - -Say it like this: - -- "Type classes without the orphan rule — overlapping instances made legal and safe." -- "Implement a capability for a type you don't own, with no newtype wrapper." -- "Overlapping implementations coexist because each is a distinct provider; the context picks one, explicitly and locally." -- "The incoherence is deliberate at the definition level and disciplined at the use site — every choice is a line in a table, never a silent resolution." - -Avoid claiming CGP is coherent, or promising global uniqueness. It deliberately drops global coherence for per-context choice, and a reader who expects "one instance per type, program-wide" will look for a guarantee CGP scopes rather than globalizes. Say plainly that uniqueness is *per context*, and frame the scoping as the point — it is what lets two contexts treat the same type two ways without conflict. - -## Generic over a type's structure, checked and free - -CGP can write code that is generic over a type's fields and variants — serialization, builders, visitors, conversions — without runtime reflection, which is the reflection payoff without the reflection cost. A type opts in with a derive, its shape becomes type-level data the trait system resolves against, and generic code recurses over that shape with full static checking and no runtime introspection, no stringly-typed field access, and nothing walked on every call. This is the selling point for the framework and tooling author, and the [reflection](../related-work/reflection.md) comparison carries the detail. - -Say it like this: - -- "Reflection's payoff without the runtime cost or the stringly-typed failures." -- "Write a framework once over any type that derives the shape — checked when you write it, not when it runs." -- "A type's structure becomes types the compiler resolves, not a descriptor you walk at runtime." - -Avoid calling CGP "a reflection system" or "reflection for Rust." A reader sold on that will look for an API to query a type and find trait bounds instead. The precise framing is "compile-time structural reflection encoded in the type system," and the honest limit — it works only on types that opted in via a derive, and does no runtime introspection — belongs in the same breath, per [skepticism](skepticism.md). - -## Abstract types chosen per context - -A quieter but powerful selling point is that CGP lets generic code name a type — an error type, a scalar, a runtime — that each context fills in for itself, through the same wiring that selects behavior. Code written against an abstract `Error` or `Runtime` works unchanged whether a context chooses `anyhow::Error` or a custom enum, Tokio or a mock, and swapping the concrete type is a wiring change that touches no provider. This unifies type selection and implementation selection into one mechanism, which resonates with the ML-module and abstract-type audience and underpins CGP's modular error handling. - -Say it like this: - -- "Generic code names the type; the context chooses it — the same wiring that picks behavior picks types." -- "Swap your error type or your runtime by changing one wiring line, touching no logic." -- "Abstract over the error type so fallible code never commits to a concrete one." - -Avoid conflating this with sealing or representation hiding. CGP's abstract types defer and configure a type; they do not hide a representation behind a type-system boundary the way an ML module's sealed type does. An ML-module reader will expect sealing, so say plainly that representation hiding is Rust's module privacy, separate from this feature — the [ML modules](../related-work/ml-modules.md) comparison makes the distinction. - -## It works on stable Rust today - -A plain but load-bearing selling point is that CGP is a library on stable Rust, not a language fork, an experimental feature, or a nightly-only trick. Much of what it delivers — type-class-style modularity, functor-style assembly, extensible data, the exactly-once fragment of effect handlers — corresponds to features that other ecosystems ship only in research languages or unmerged proposals, and CGP provides them as a crate you can add to a project now. This matters most to the evaluator weighing adoption risk and to the functional-programming reader who assumes this level of expressiveness requires a different language. - -Say it like this: - -- "A library on stable Rust — not a fork, not a nightly feature, not a proposal." -- "The expressiveness you'd reach for another language to get, available as a crate today." - -Avoid overstating maturity while making this point. "Works on stable Rust today" is true and worth saying; "production-proven at scale" is a different claim that needs evidence, and the evaluator reader will hear the gap. Keep this selling point to what it is — availability without a toolchain gamble — and let the maturity conversation happen honestly where [skepticism](skepticism.md) handles it. - -## Audience-tuned one-liners - -The sharpest phrasings are the ones that translate a selling point into the exact idiom of a reader's background, because they let the reader reuse everything they already know and spend their attention only on what is new. Each of the following is calibrated to one profile from [reader-profiles.md](reader-profiles.md) and grounded in the matching [related-work](../related-work/README.md) comparison; reach for the one that fits the piece's audience and avoid using it on a different audience, where it will misfire. - -- For the **Scala or implicits reader**: "implicits without the mystery — the dependency still arrives without threading it through every call, but which implementation supplies it is a line in a wiring table, not a resolution search." See [implicit parameters](../related-work/implicit-parameters.md). -- For the **Haskell or type-class reader**: "type classes without the orphan rule, and overlapping instances made legal." See [type classes](../related-work/type-classes.md). -- For the **Spring, Guice, or Dagger reader**: "Dagger, taken further — compile-time-checked injection, with per-context choice a single global binding graph can't express." See [dependency injection](../related-work/dependency-injection.md). -- For the **dynamic-language reader**: "duck typing that can't blow up at runtime, and dynamic dispatch that costs nothing." See [dynamic dispatch](../related-work/dynamic-dispatch.md). -- For the **OCaml or ML-module reader**: "functors with the plumbing automated — a declarative table instead of a hand-ordered functor chain." See [ML modules](../related-work/ml-modules.md). -- For the **algebraic-effects reader**: "effect handlers minus the continuation, resolved by type instead of dynamic scope, and extended to abstract types." See [algebraic effects](../related-work/algebraic-effects.md). -- For the **reflection or framework-author reader**: "compile-time reflection encoded in the type system — generic over a type's structure, checked when you write it, erased before it runs." See [reflection](../related-work/reflection.md). -- For the **PureScript or row-types reader**: "row polymorphism's extensibility, in a nominal systems language, opt-in per type." See [row polymorphism](../related-work/row-polymorphism.md). - -## Phrasing principles - -Across every selling point, a few wording rules hold, and they consolidate the per-section guidance into a checklist a writer can run before publishing. Prefer the concrete capability to the adjective — "compiled to a direct call" over "fast," "one interface with many implementations" over "flexible," "checked at compile time" over "safe" — because the concrete version is both more believable and more informative. State the honest limit in the same breath as the claim when the audience is skeptical, since the skeptic is scanning for the omission and trusts the writer who volunteers it. - -Certain words reliably cause the misunderstandings the [skepticism](skepticism.md) document exists to prevent, and they should be avoided by default. Do not say CGP "automatically resolves," "finds," or "figures out" the implementation — it is explicit, and this is the single most common misframing. Do not call it "magic," a word this audience treats as a warning rather than a wonder. Do not flatly call it "a DI framework," "a reflection system," or "an effect system" — each invites the reader to expect runtime behavior CGP does not have; qualify each with "compile-time" and the distinguishing limit. Do not promise "no boilerplate," "replaces traits," or any unbenchmarked "faster than"; the true, smaller claims — "moves boilerplate into one place," "a superset of traits," "no runtime cost" — are the ones that survive scrutiny and, with this audience, persuade more for surviving it. diff --git a/docs/communication-strategy/skepticism.md b/docs/communication-strategy/skepticism.md deleted file mode 100644 index b83a6a5b..00000000 --- a/docs/communication-strategy/skepticism.md +++ /dev/null @@ -1,123 +0,0 @@ -# Skepticism - -This document catalogs the skepticisms a reader brings to CGP, judges honestly whether each is justified, and prescribes the wording that answers it without triggering the misunderstanding that fed it. - -## How to use this document - -Skepticism is the default reaction to CGP, not the exception, so a writer's job is less to avoid objections than to meet them before the reader raises them. The skepticisms come from three sources, and telling them apart decides how to respond. Some are **imported** from a paradigm CGP resembles — the reader was burned by dependency-injection magic or spooky implicit resolution and transfers that wound to CGP on sight. Some are **native** to the Rust community's culture, which is proudly wary of complexity, macros, and abstraction that does not earn its keep. And some point at **genuine costs** CGP really carries, which no wording can spin away and which the writer must concede plainly. The worst mistake is treating a justified skepticism as a misunderstanding to argue down; the reader knows the difference, and the attempt destroys the trust the [selling-points](selling-points.md) rest on. - -For each skepticism below, the document names where it comes from, judges whether it is justified — fully, partly, or only through a misunderstanding — and gives the wording that responds. Two response patterns recur. When a skepticism rests on a **misunderstanding** (usually an assumption that CGP behaves at runtime like the thing it resembles), the fix is precise wording that heads off the wrong mental model before it forms. When a skepticism rests on a **real cost**, the fix is to concede it, then reframe it as the deliberate price of a benefit the reader values — the trade the reader would make anyway if they saw it whole. This document is the mirror of [selling-points.md](selling-points.md); most objections here answer a claim there, and the two must stay consistent so a writer is never told to say a phrase that the other warns against. - -## Skepticisms imported from other paradigms - -Many readers meet CGP already carrying a grievance against a concept it resembles, and their skepticism is really about that concept. The related-work documents record these grievances in depth and with citations; the task here is to recognize which imported wound is firing and to word CGP so the reader does not simply re-live it. In almost every case the reader's original complaint is *justified about the other tool* and *misapplied to CGP*, and the wording must grant the first while correcting the second — never by dismissing their experience, always by showing where CGP's design diverges from the thing that hurt them. - -### "This is dependency injection, and DI is heavy, magic, and fails at runtime" - -This skepticism comes from the enterprise reader's real scars: reflection-based containers that hide dependencies, fail at startup with an exception, cost time scanning a classpath, and behave as "magic" no one can debug. The complaint is thoroughly justified about the frameworks that provoked it, and entirely misapplied to CGP, which has no container, no reflection, and no runtime graph — its wiring is resolved by the compiler and compiled away, and a missing dependency is a compile error at the wiring site. The danger is that flatly calling CGP "dependency injection" makes the reader import the whole runtime picture, cost and failure mode included. - -Word it by naming the difference before the category. Say "compile-time, reflection-free dependency injection" rather than "dependency injection," and defuse the runtime-container assumption explicitly: there is no object holding a graph, the container is the type system, and resolution happens during compilation. Lead with the pains they resent — hidden dependencies, the startup `NullPointerException`, the "magic" they cannot trace — and show CGP answers each by construction. The framing that lands, per [dependency injection](../related-work/dependency-injection.md), is "Dagger, taken further": a reader who accepts Dagger's compile-time-checked, reflection-free injection already accepts CGP's bargain. - -### "Implicit resolution is spooky — values appear from nowhere" - -This comes from the Scala or Haskell reader who has spent an afternoon tracking down which `given` was selected or why an expected instance was not, and it is justified about implicit resolution: the mechanism's generality is exactly what makes it hard to follow. It does *not* transfer to CGP, but for a reason the writer must state carefully, because the obvious reassurance is a trap. CGP does not have spooky resolution because it does not resolve automatically at all — the provider is named explicitly in a wiring table. The misunderstanding to prevent here is the opposite of the usual one: the reader may hope CGP "just finds" the implementation, and it does not. - -Word it as "implicits without the mystery," then immediately anchor the honesty: the dependency still arrives without being threaded through every call, but *which* implementation supplies it is a line you can read, not the outcome of a search. Never promise automatic resolution — a reader sold on "CGP finds the right one" feels misled at the first `delegate_components!` entry, and the misframing also undercuts the coherence-freedom selling point that depends on there being no global search. The [implicit parameters](../related-work/implicit-parameters.md) comparison frames the trade: explicit wiring is the feature, not a shortfall. - -### "Coherence exists for a reason — incoherent instances are dangerous" - -This is the informed skepticism of the Haskell or advanced-Rust reader who knows that global coherence is what keeps a `Set` from being corrupted by two orderings, and that Haskell's `INCOHERENT` and Lean's diamonds let a wrong instance be chosen silently. It is justified in general — dropping coherence naively *is* dangerous — and it is the objection most worth engaging on its merits rather than deflecting, because the reader raising it is precisely the one who can become an advocate or a detractor. CGP's answer is that it drops global coherence but not safety: because every provider choice is explicit and per-context, incoherence never means the silent, searched-and-wrong selection that makes the extensions dangerous. - -Word it by conceding the general point and locating CGP's discipline precisely. Say that CGP is deliberately incoherent at the definition level and disciplined at the use site — overlapping providers coexist, but a context names exactly one, explicitly and locally, so the choice can never be silently derailed by overlap. Do not claim CGP is coherent, and do not promise program-wide uniqueness; say plainly that uniqueness is per context, and frame that scoping as the point. The [type classes](../related-work/type-classes.md) and [bypassing coherence](../concepts/coherence.md) documents carry the full argument, including the honest case where genuine global uniqueness is what a program wants and coherent type classes are the better tool. - -### "Extensible records and rows mean gigantic error messages and specialist complexity" - -This comes from the PureScript or Gleam reader who has seen row-unification errors clog a terminal and watched extensible records earn a reputation as a tool you regret reaching for. It is *partly justified* against CGP, and this is one to concede rather than wave away: CGP's structural operations, when mis-wired, do surface as long, generated-type-heavy trait errors — the CGP-idiom echo of the row-unification message that pushed two languages away from rows. What CGP changes is that the shape is opt-in per type rather than pervasive, and that [`check_components!`](../reference/macros/check_components.md) forces a missing field or variant to be named at the wiring site instead of deep inside a use. - -Word it by owning the diagnostic cost and bounding it. Say that CGP inherits verbose trait errors, that a check localizes them to the wiring site, and that because the structural view is opt-in you pay the complexity and the error-message tax only where you use the power — not across the whole program, as a row kind would impose. Do not claim the errors are clean; the reader has seen this movie and will trust the writer who admits the ending. The [row polymorphism](../related-work/row-polymorphism.md) comparison frames it as extensibility brought to a nominal language, paid for only where used. - -### "Isn't this just an effect system / a reflection system?" - -These two come from readers fluent in algebraic effects or in reflection, and both are misunderstandings that a careless pitch invites. The effects reader will look for continuations — `resume`, multi-shot handlers, generators, async — and CGP has none; a provider is an ordinary function that returns exactly once. The reflection reader will look for an API to query a type at runtime, and CGP has none; the structure is types the compiler resolves, and only for types that opted in via a derive. Neither skepticism is a real deficiency, because CGP never claimed to be either thing — but a writer who calls it "an effect system" or "reflection for Rust" creates the false expectation and then owns the reader's disappointment. - -Word it by naming what CGP is with precision and stating the limit up front. For effects: "the exactly-once, resume-in-place fragment of effect handlers — dynamic binding, made static and per-context," with the plain admission that continuations, generators, and async are outside it (see [algebraic effects](../related-work/algebraic-effects.md)). For reflection: "compile-time structural reflection encoded in the type system," with the plain admission that it does no runtime introspection and works only on types that derive the shape (see [reflection](../related-work/reflection.md)). In both cases the honesty is the persuasion: a reader who knows how much machinery exists to tame continuations, or how much runtime cost reflection carries, hears "we don't do that part" as a considered trade. - -### "Dynamic dispatch is flexible — won't a static version lose what makes it useful?" - -This comes from the dynamic-language reader who values runtime openness: heterogeneous collections, plugins loaded at startup, live redefinition, metaprogramming. Their skepticism is *justified and must be granted fully*, because CGP genuinely cannot do these things — it resolves at compile time, so there is no runtime object graph to mutate and no runtime dispatch to intercept. What CGP offers instead is the decoupling of dynamic dispatch with none of its cost or its runtime failure modes, which is a different bargain, not a replacement for runtime dynamism. - -Word it by drawing the line honestly and selling what is on CGP's side of it. Say plainly that runtime openness — plugins, heterogeneous collections, monkey-patching — lives on the runtime side, where Rust's own `dyn Trait` and the dynamic languages remain the right tools, and that CGP is for the case where the set of implementations is known at build time. Then offer the pair this reader most wishes for: "duck typing that can't throw at runtime" and "dynamic dispatch that costs nothing." Frame CGP's late binding as late to the *wiring site*, not to runtime — the flexibility is real but spent at compile time. The [dynamic dispatch](../related-work/dynamic-dispatch.md) comparison holds the full mapping. - -## Skepticisms native to the Rust audience - -Beyond the imported wounds, the Rust community brings its own well-earned wariness, and these skepticisms fire even for a reader with no functional-programming or enterprise background. This culture has watched abstraction ruin codebases and macros obscure behavior, and it treats "clever" as a criticism. Several of these skepticisms are partly or fully justified, and the writing must earn trust by conceding the true part rather than by insisting the complexity is free. - -### "This is too clever / over-engineered / astronaut architecture" - -This is the reflex of the pragmatic majority, and it is a reasonable prior rather than a misunderstanding: most abstractions that announce a new paradigm do not earn their cost, and the reader is right to demand that CGP prove it does. The skepticism is justified as a *default to be overcome with evidence*, not as a verdict. The failure mode is answering it with more enthusiasm about the paradigm, which confirms exactly the fear. - -Word the response as problem-first restraint. Lead with a concrete, familiar pain and show it gone in a before/after on ordinary-looking code; let the modularity justify itself by removing something real rather than by being impressive. Say explicitly when *not* to reach for CGP — a capability with one implementation does not need it — because naming the boundary is what proves the tool is not being oversold. The reader who sees the author decline to apply CGP everywhere believes the author about where it does belong. - -### "Macros are magic — I can't see what they generate or debug it" - -This skepticism is partly justified: CGP is macro-driven, and generated code is genuinely harder to inspect and step through than hand-written code, which the macro-wary Rust reader is right to weigh. What tempers it is that the expansions are documented and deterministic, the generated traits and impls are ordinary Rust once emitted, and a provider can be written and read without ever seeing the macro output. But the honest position concedes that debugging generated code is a real cost. - -Word it by replacing "magic" with "explicit" and pointing at the seams. Never use the word "magic" approvingly — this audience reads it as a warning — and instead emphasize that the wiring is a table you write and read, the dependencies are declared, and the expansion is specified. Concede that generated code is harder to trace, and offer the mitigations honestly: the expansions are documented, and the vanilla-looking idioms keep most code readable without expansion. Overclaiming transparency here loses the exact reader you are addressing. - -### "I can't tell which code actually runs on a method call" - -This is a specific, well-earned worry distinct from the macro complaint, and it appeared verbatim in CGP's own community discussion (see [attention-and-engagement.md](attention-and-engagement.md)): a reader fears that with wiring indirection it becomes "basically impossible to tell which particular bit of code will be entered on a method call," which makes a codebase hard to navigate. It is *partly justified* — CGP does add a layer between a consumer-trait call and the provider that answers it, so a reader tracing execution has one more hop to follow than with a direct call, and denying that indirection would be dishonest. - -Word it by conceding the hop and pointing at the map. The indirection is real, but unlike runtime dynamic dispatch it is *statically resolved and explicit*: the [`delegate_components!`](../reference/macros/delegate_components.md) table is a single, greppable place that names exactly one provider for each component, so "which code runs" is a table lookup that a reader — or an IDE's go-to-definition — follows at compile time, not a runtime search over registered handlers. Frame the wiring table as the navigation aid it is, the one location that answers "what implements this here," rather than pretending the indirection is absent. This is the same explicitness that answers the implicit-resolution and "magic" objections and that the [swap-implementations selling point](selling-points.md) calls "one greppable place": the choice is written down, in one spot, and can never be silently redirected. - -### "What does this do to compile times?" - -This is a justified and specific concern, not a reflex: trait-resolution-heavy code and macro expansion do add compile-time work, and a reader who has waited on a slow Rust build is right to ask. Pretending otherwise is the kind of dodge that ends trust. - -Word it by conceding the direction and declining to invent a magnitude. Acknowledge that CGP adds compile-time work, do not quote a number you cannot cite, and reframe against the runtime it replaces: the resolution that costs at compile time is resolution that would otherwise cost at runtime or not be checked at all. If a concrete measurement exists for the piece at hand, cite it; if not, keep the claim qualitative and honest rather than reassuring and unsupported. - -### "The error messages are a wall of generated types" - -This is justified, it is the cost most likely to bite a real user, and for most of CGP's life it had to be conceded as a standing sharp edge — but that has changed, and the response is no longer "this is the price you pay" but "this is being fixed directly, use the tool." A mis-wired context can still produce long, generated-type-heavy diagnostics under plain `cargo check`, and a reader who has hit one will not believe a writer who pretends the raw experience is pleasant, so the concession stays. What is new is that CGP now ships a dedicated answer to exactly this pain, which downgrades the objection from a conceded cost to a cost actively being removed. - -Word it by conceding the raw diagnostics and then pointing at [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's error toolchain. Say that CGP's raw errors can be verbose, that [`check_components!`](../reference/macros/check_components.md) forces the real cause — a missing field, type, or dependency — to be named at the wiring site, and that `cargo cgp check` goes further: it turns on Rust's next-generation trait solver to **un-hide** the cause the default solver suppresses (the worst class, where the compiler names no missing field at all), then leads with that root cause, tags it with a `[CGP-Exxx]` code, and draws the dependency chain as a tree. The claim to make is that a dedicated checker leads with the root cause — not that CGP's errors are now as clear as any other Rust error's. - -Concede the tool's youth in the same breath, because the reader can install it and check within the hour. `cargo-cgp` is v0.1.0-alpha: it already reshapes the core wiring-error classes but does not yet cover every class, and some still pass through as the compiler wrote them. The honest frame is that the error experience is dramatically better and actively improving — not solved. That candor is what makes the rebuttal land; claiming the diagnostics are now good, or the problem finished, is the fastest way to lose the technical reader. The [selling-points](selling-points.md) mirror carries the capability side of this same claim, and the [error catalog](../errors/README.md) the class-by-class detail. - -### "Rust doesn't need dependency injection — that's a Java-ism" - -This is distinct from the enterprise reader's imported fear of runtime DI magic, and it is the *native* Rust version: the pragmatist who has read that idiomatic Rust already does dependency injection with plain traits and generics, and who therefore treats the whole category — and any crate that markets itself with the term — as an unwanted import from the Java and Spring world. The sentiment is real and widespread, and it is *partly justified*: for a great many cases, passing a trait-bounded generic or a trait object genuinely is the right tool, and a DI *framework* would be over-engineering, which is why Rust's own DI crates stay niche (see [attention-and-engagement.md](attention-and-engagement.md)). The trap is that flatly pitching CGP as "dependency injection" invites this reader to file it under "solution to a problem Rust doesn't have" and move on. - -Word it by agreeing, not arguing. Concede that Rust does not need a DI *framework* and that CGP is not one — it is the traits-and-generics approach the reader already endorses, carried to the cases where doing it by hand stops scaling. Then locate those cases concretely: when a capability needs several interchangeable implementations chosen per context, plain generics hit the coherence wall that forces the marker-struct workaround developers already hand-roll ([attention-and-engagement.md](attention-and-engagement.md)); when the dependency lives on a type you do not own, the orphan rule blocks it; and when a public trait carries its dependencies in its signature, it leaks internal types into the public API, the encapsulation cost documented in [selling-points.md](selling-points.md). CGP earns its place at exactly those boundaries, and nowhere below them. The honest frame, per [dependency injection](../related-work/dependency-injection.md), is that CGP is the trait-based approach this reader already prefers, with the boilerplate and the coherence contortions removed — not a framework asking them to think like a Java developer. - -### "Why not just use traits, generics, or an enum?" - -This is the sharp, fair challenge of a competent Rust developer, and often the right question: for many problems, plain traits or an enum genuinely are the better tool, and CGP would be over-engineering. The skepticism is justified whenever the problem has one implementation or a closed, small set of them, and the writing must not pretend CGP wins universally. - -Word it by conceding the range where the plainer tool wins and drawing the line where CGP starts to pay. Say that a single implementation belongs in an ordinary trait, a closed variant set in an enum, and that CGP earns its cost when a capability needs several interchangeable implementations chosen per context, or an implementation for a type you do not own, or coherence-free overlap — the cases plain traits handle awkwardly or not at all. The [modularity hierarchy](../concepts/modularity-hierarchy.md) is the honest map of when to climb from a plain trait to a full component, and pointing to it signals that CGP is a rung to reach for deliberately, not a default. - -### "It's immature — can I bet a codebase on it?" - -This is the evaluator's central, justified concern, and it is about risk the reader would own: a young paradigm, an evolving ecosystem, and the cost of a team learning something novel. It cannot be answered by enthusiasm, and it should not be minimized. - -Word it with candor and with the escape hatches. Be honest that CGP is young and that adoption is a real decision, then lower the stakes truthfully: it is a superset of ordinary traits, so it can be adopted incrementally in one corner and stepped back from without rewriting a codebase, and it imposes no runtime, so it does not lock a project into a framework's lifecycle. Address the "can my team learn this" question directly rather than letting it linger. The evaluator discounts hype by profession, per [reader-profiles.md](reader-profiles.md), so the only register that works is level. - -### "There's a learning curve" - -This is justified and should simply be granted: the consumer/provider split, wiring, and coherence-bypassing are a genuine conceptual load, and a reader who suspects a ramp-up is right. Denying it insults the reader; the useful move is to shrink the *first* step rather than the whole curve. - -Word it by separating the on-ramp from the depth. Say that the first useful thing — a capability defined as a function, wired once — takes only ordinary Rust knowledge, and that the deeper machinery can be learned as needed rather than up front. Point the newcomer at the gentle path, not the full reference. The honest message is that the curve is real but not a cliff, and that a reader can be productive well before they understand the whole system. - -## A careful-wording checklist - -The skepticisms above share a small set of wording failures that reliably create or inflame them, and a writer can catch most problems by scanning a draft against this list before publishing. The through-line is that vague or grand wording invites the reader to supply the worst interpretation, while precise wording forecloses it — and that conceding a real cost in plain words buys more trust than any amount of polish. - -The words and moves to avoid, each paired with what to do instead, are worth keeping in view: - -- Do not say CGP "automatically resolves," "finds," or "figures out" an implementation. Say the provider is named explicitly, in one readable place — the explicitness is the feature. -- Do not use "magic," even admiringly. Say "explicit," and point at the wiring table and the declared dependencies. -- Do not flatly call CGP "a DI framework," "a reflection system," or "an effect system." Qualify each — "compile-time, reflection-free dependency injection," "compile-time structural reflection," "the dynamic-binding fragment of effect handlers" — and state the distinguishing limit in the same breath. -- Do not imply any runtime component. Say the wiring is resolved at compile time and erased; a wired call is a direct call. -- Do not claim "no boilerplate," "replaces traits," or an unbenchmarked "faster." Say "moves boilerplate into one place," "a superset of traits," and "no runtime cost." -- Do not pretend the diagnostics are clean or the compile-time cost is nil. Concede both — and for the diagnostics, point at [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), which un-hides and leads with the root cause for the classes it recognizes while conceding it is a v0.1.0-alpha that does not yet reshape them all; name [`check_components!`](../reference/macros/check_components.md) and the compile-time-versus-runtime trade as the other honest mitigations. -- Do not sell CGP as universally better than plain traits, or promise runtime dynamism it lacks. Name where the plainer tool or a runtime mechanism wins; the concession is what makes the rest believable. diff --git a/docs/communication-strategy/tag-lines.md b/docs/communication-strategy/tag-lines.md deleted file mode 100644 index dc301bbd..00000000 --- a/docs/communication-strategy/tag-lines.md +++ /dev/null @@ -1,89 +0,0 @@ -# Tag line - -This document records CGP's chosen tag line, the analysis that justifies every word of it, the high-level pitch that should follow it, and model introductions that put the two to work for a new audience. - -## The chosen tag line - -CGP's front-page tag line is: - -> **A language extension for Rust, with pluggable trait implementations at compile-time.** - -This is the settled lead descriptor for landing pages, READMEs, talks, and posts — the one line to open with when a reader meets CGP for the first time, and the phrasing every other piece of public writing should echo rather than reinvent. It is chosen, not merely proposed, but its real grade is still the reaction it draws: a tag line's efficacy is empirical, and publication is the measurement, so treat the line as fixed for consistency's sake while watching which questions and dismissals it attracts in the channels where the [reader profiles](reader-profiles.md) gather, per [attention-and-engagement.md](attention-and-engagement.md). The rest of this document explains why this line was chosen, what to say immediately after it, and how to weave the two into an opening a newcomer can act on. - -## Why the tag line carries this much weight - -The tag line shapes CGP's reception more than any document beneath it, because it is the first thing a reader learns and often the only thing. In a handful of words it has to do two jobs at once — gain attention and prevent misunderstanding — for an audience that ranges from a skimmer giving it three seconds to an expert who will pick the wording apart. It is the compressed form of everything in [selling-points.md](selling-points.md) and [skepticism.md](skepticism.md): a line that wins the skimmer but reads as an overclaim to the expert has failed, and so has one that satisfies the expert but gives the skimmer nothing to act on. That double burden is why the exact words are worth this much scrutiny, and why the analysis below weighs each one. - -## How a tag line is judged - -A candidate line is judged on three axes, and the third most often kills an otherwise attractive one. The first is **attention**: does it hook, and whom — a line that excites the type-system enthusiast may leave the pragmatist cold. The second is **skepticism**: what aversion or misreading it triggers, and whom it repels — the loaded word, the overclaim, the frame that invites a dismissal. The third is **feasibility**, meaning honesty: is the claim accurate and defensible to the audience most able to check it, because the honesty-is-the-strategy rule of this section means an overclaiming line does more damage than a modest one, since the readers worth winning are exactly the ones who will catch it. - -Three further rules break ties and shape how the line is deployed. A **name is not a pitch**: a coined term tells a cold reader nothing, so it is paired with a plain-language descriptor rather than shipped alone. A tag line can be **layered**: a short lead carries the attention and a subline carries the honest mechanism, so the two jobs need not fight for the same words. And the decisive test is that a line is **checked against the skeptic and the skimmer first**, because they are the least forgiving and most numerous readers in [reader-profiles.md](reader-profiles.md), and a line that survives them survives everyone. - -## Historical background — "a modular programming paradigm for Rust" - -The chosen line replaces a long-serving predecessor, and understanding why that one is retired is the clearest way to see what the new one is built to fix. For most of the project's life CGP described itself as **"a modular programming paradigm for Rust."** That line was honest and safe, and it had genuine virtues: it described CGP as a way of *writing* rather than a runtime, it overclaimed nothing, and it resonated with the architecture-minded reader who already values decoupling. As a fallback it is still unimpeachable on the feasibility axis. - -It underperformed on the other two axes for two separable reasons, and both carry a lesson the new line answers. First, it **undersold what CGP had become**. The project began as a way to make trait implementations modular, but it has since grown abstract types, extensible data, a handler and computation family, namespaces, and more, until it reads less like one technique and more like a broad extension to the language — a breadth "a paradigm" quietly hides. Second, and more damaging, its **lead word repelled part of the audience on contact**. "Modularity" is an abstraction few developers wake up wanting, "paradigm" reads as academic, and for a large slice of the audience "modular" carries active baggage from runtime dependency-injection frameworks — the hidden dependencies, the configuration weight, the "magic" documented in [dependency injection](../related-work/dependency-injection.md) — so the word meant to attract instead signaled over-engineering to the very pragmatists CGP most needs to win, the reflex catalogued in [skepticism.md](skepticism.md). - -The standing lesson is therefore to **retire "modular" as a lead word**. It is the weakest hook of any CGP has tried and the only one that actively repels, so modularity survives only as a supporting descriptor, and when it is used it must be immediately differentiated from its framework connotations by the three properties that distinguish CGP from a runtime container — "compile-time," "zero-cost," and "explicit." The chosen line puts that lesson into practice: it keeps the honest "a way of writing Rust" spirit of the incumbent while leading with words that pull the audience in rather than push part of it away. - -## Why the chosen line works, word by word - -The chosen line was assembled so that each part does one job the others cannot, and reading it word by word shows why no piece can be dropped. Taken together, "a language extension for Rust, with pluggable trait implementations at compile-time" carries breadth, a concrete hook, and a misreading-defuser in a single sentence — the three things the analysis of every weaker option showed a tag line has to hold at once. - -**"A language extension for Rust"** captures the ambition honestly. "Extension" signals that CGP significantly enlarges what the language can express — matching the breadth the incumbent hid — while implying *addition to* rather than *replacement of*, so it supports gradual adoption instead of fighting it. It is also the defensible version of a bolder framing that was rejected: calling CGP "a language of its own" or "a superset of Rust" overclaims, because CGP is a set of procedural macros that desugar to ordinary Rust and compile on the stable toolchain — it adds no grammar — and the precise, vocal slice of the Rust audience will answer "it's not a language, it's a macro library" and win the exchange before it begins. "Extension" avoids that trap: an embedded, macro-based DSL is fairly called a language extension in ordinary usage, so the word keeps the ambition while staying on the right side of the honesty axis. - -**"Pluggable"** names the actual novelty, and the word was chosen against two alternatives that miss it. It is *not* "reusable," because to a Rust developer traits are already the reuse mechanism — one interface, many types — so "reusable trait implementations" describes something they believe they already have and points at the wrong axis. It is *not* "swappable," which says the right thing but is a less established software term and reads as a coinage next to a word of art. "Pluggable" names the real capability: one interface with many interchangeable implementations, each chosen per context — the thing Rust's coherence rules and the orphan rule normally forbid, and the reason CGP exists. - -**"Trait implementations"** grounds the abstraction in something concrete. It ties the broad "extension" framing to the trait system a working Rust developer uses every day, so the reader has a mental picture to attach the promise to rather than an abstraction to decode. It is the concrete hook the incumbent's "paradigm" lacked. - -**"At compile-time"** does two jobs. It defuses the runtime-dependency-injection misreading that "pluggable" might otherwise invite — the hidden-container, startup-configuration, reflection-cost fear — by stating up front that the choice is resolved statically. And it creates a productive tension with "pluggable" that *is* CGP's pitch in miniature: for a Rust reader "pluggable" normally implies runtime machinery — `dyn Trait`, a vtable, a plugin loaded at startup — so following it immediately with "at compile-time" says *plugin-style swapping, resolved statically, at zero runtime cost*. That tension is the part "reusable" could never set up, and it is why the qualifier is load-bearing rather than decorative. - -The line also makes one deliberate omission and carries one residual risk, both handled downstream. It does **not lead with the owned name**, "context-generic programming," because a name is not a pitch and the phrase is opaque on first contact — observed, not hypothetical, in CGP's own release discussions where readers said it "obscures more than it conveys" ([attention-and-engagement.md](attention-and-engagement.md)). The name is introduced *after* a concrete capability has landed, always paired with a descriptor, per [vocabulary.md](vocabulary.md). The residual risk is that "trait implementations" is narrower than "language extension" promises — it says nothing of the abstract types, extensible data, and handler family that make CGP broad — so the line slightly undersells its own breadth. That gap is closed not by cramming more into the tag line but by the pitch that follows it, below. - -## What to say after the tag line — building the high-level pitch - -The tag line earns the reader's next few seconds; the lines immediately after it are where attention is converted into understanding, and they should descend a deliberate funnel from reassurance to concrete pain to the owned name. The tag line is the top of that funnel, not the whole of it, so a piece that stops at the line leaves both the skeptic's first objection and the reader's "so what" unanswered. Build the pitch in the order below, taking as many rungs as the format's patience allows and stopping wherever it runs out. - -The first line after the tag line is the **reassurance subline**, and it does the skeptic's work before the skeptic can. It states the mechanism the hook left out — "still ordinary Rust, on a stable toolchain, with no runtime cost, adopted one trait at a time" — which heads off the two misreadings the line is most prone to: that "language extension" means a new language to learn (it does not — it is a library you add gradually) and that "pluggable" means a runtime framework (it does not — the resolution is static). This is the layered form the judging rules call for: the tag line hooks, the subline defuses. - -The second rung is the **breadth line**, which repays the tag line's one debt. Because "trait implementations" undersells how far CGP reaches, a single sentence should name the rest of the surface — that beyond swappable trait implementations, CGP adds abstract types a context chooses for itself, extensible records and variants, and a family of composable handler and computation components. This turns "an extension for one thing" into "an extension broad enough to earn the word," without weighing down the line itself. - -The third rung, and the most persuasive, is a **concrete pain** — a problem the reader has already felt, stated before any mechanism. Lead with a felt frustration and let CGP be the answer to it: wanting a fake implementation for tests and the real one in production without a dependency-injection framework; needing to implement a trait for a type you do not own and hitting the orphan rule; watching a single trait grow into a monolith because there was no way to split its implementations. These before-and-after stories live in [problems-solved.md](problems-solved.md), and one of them, chosen for the channel's dominant reader, should almost always precede the abstract description of what CGP *is*. - -The final rungs introduce the **owned name** and the **call to action**. Only after a concrete capability has landed should the piece name the paradigm — "this is what we call *context-generic programming*" — always beside a plain descriptor, so the name attaches to an understanding the reader now holds rather than standing in for one they do not. The call to action is then matched to the reader's stage in the funnel: a quickstart for the skimmer ready to try it, a link to an honest maturity-and-cost discussion for the evaluator weighing a bet, and never a request to adopt a codebase from a reader who has known CGP for thirty seconds. - -One devrel note governs the whole sequence: concede the cost somewhere in it. CGP is more machinery than a plain trait, and for a capability with a single implementation a plain trait is the better tool; saying so plainly is what makes the rest of the pitch believable to an audience expert at detecting spin, and it costs nothing because it is true. - -## Candidate introductions - -The drafts below show the tag line and the pitch working together as opening copy for a newcomer, each tuned to a different venue and dominant reader, with a short note on the moves it makes. They are templates for the moves, not approved words: adapting one to a real piece means re-running the decisions for that channel's reader — above all swapping in the concrete pain that reader feels most sharply — rather than find-and-replacing the copy. Every CGP claim in them is bound by the knowledge base's synchronization rule, so verify each against the source and the `/cgp` skill before it ships. - -**For a README or landing page, above the fold — skimmer and evaluator.** This intro leads with the line, reassures on the toolchain gamble immediately, and shows breadth before the reader scrolls. - -> CGP is a language extension for Rust, with pluggable trait implementations at compile-time. It is an ordinary library on stable Rust — no forked compiler, no new toolchain — that lets one trait have many interchangeable implementations and lets each type choose which one it uses. You write that choice as wiring in one place, and the compiler resolves it statically, so there is no runtime cost and nothing to configure at startup. The same machinery gives you abstract types each context picks for itself, extensible records and variants, and a family of composable handlers — and you can adopt any of it one trait at a time, leaving the rest of your code exactly as it is. - -*Moves: tag line verbatim; reassurance subline ("ordinary library on stable Rust," "resolved statically," "no runtime cost"); breadth line naming abstract types, extensible data, and handlers; gradual-adoption close. Holds the concrete code for the screen just below.* - -**For a launch post — the pragmatic skimmer, led by pain.** This intro opens on a frustration the reader has felt and makes the tag line the resolution, defusing the "another DI framework" reflex in the same breath. - -> If you have ever wanted two implementations of the same trait — a fake for your tests and the real thing in production — you have met the wall Rust puts up: one implementation per type, and no implementing a trait you do not own for a type you do not own. CGP takes that wall down. It is a language extension for Rust that gives you pluggable trait implementations, chosen per type and resolved entirely at compile-time, with no dependency-injection framework and not a drop of runtime overhead. It is a library on the stable toolchain; you can add it to a single module and leave everything else untouched. - -*Moves: opens on the mock-versus-real and orphan-rule pains from [problems-solved.md](problems-solved.md); tag line woven in as the answer, not the opener; "no dependency-injection framework" and "at compile-time" preempt the runtime-DI dismissal; gradual-adoption reassurance closes.* - -**For a talk or a spoken introduction — a general audience, curse-of-knowledge guarded.** This intro is plain enough to say aloud, defines the load-bearing word, and defers the name. - -> Here is the shortest way I can put it. CGP is a language extension for Rust, with pluggable trait implementations at compile-time. "Pluggable" is the word that matters: in ordinary Rust a trait has one implementation per type, but with CGP you can write several and plug in whichever one a given context should use — and because the plugging happens at compile-time, it costs nothing when the program runs. It is all still ordinary Rust on the stable toolchain. Under the hood we call this context-generic programming, but you do not need the name to use it. - -*Moves: tag line stated, then its key word unpacked with the one-impl-per-type contrast; "at compile-time" reframed as the zero-cost promise; the owned name introduced last and explicitly marked optional; every sentence plain enough to speak.* - -**For a comparison piece or a design write-up — the evaluator, cost conceded.** This intro names the paradigm early for a reader who wants the category, explains the core idea in one sentence, and draws CGP's boundary honestly. - -> CGP — context-generic programming — is a language extension for Rust that gives you pluggable trait implementations at compile-time. The idea underneath is small: separate the trait you *call* from the trait you *implement*, and let each context wire the two together, so one interface can have as many implementations as you like while every context picks its own. It all desugars to ordinary trait impls on stable Rust and is resolved statically, so the flexibility costs nothing at runtime. It is also more machinery than a plain trait, and for a capability that will only ever have one implementation a plain trait is still the right choice — CGP earns its keep when you genuinely need more than one, or need to choose per context. - -*Moves: name introduced up front because this reader wants the category; the consumer/provider split stated in one plain sentence; the compile-time, zero-cost reassurance; and an explicit concession of where a plainer tool wins, per [positioning.md](positioning.md), which is what earns this reader's trust.* - -## Keeping this document in sync - -Because the tag line is now settled rather than under selection, any change to it ripples across the section and must be propagated in the same change. The line and its layered pitch appear as guidance in [formats.md](formats.md) and as finished copy in [worked-examples.md](worked-examples.md); the wording rules that justify it — retire "modular" as a lead word, prefer "extension" over "a language"/"a superset," never ship the name alone — are enforced in [vocabulary.md](vocabulary.md) and [key-features.md](key-features.md); and the empirical caveat that publication is the real measurement lives in [attention-and-engagement.md](attention-and-engagement.md). When the line changes, revisit each of those. The catalog entry for this document in [README.md](README.md) must also match its current contents. diff --git a/docs/communication-strategy/technical-barriers.md b/docs/communication-strategy/technical-barriers.md deleted file mode 100644 index 983f601c..00000000 --- a/docs/communication-strategy/technical-barriers.md +++ /dev/null @@ -1,74 +0,0 @@ -# Technical barriers - -This document maps the technical barriers a reader faces when trying to understand CGP, and the design affordances and teaching moves that lower each one, so that writing about CGP meets readers below the barrier rather than above it. - -## Comprehension is a separate problem from skepticism - -A reader can genuinely *want* to understand CGP and still fail to, because its concepts sit on a stack of Rust knowledge that not every developer has — and that comprehension barrier is a different problem from the one [skepticism.md](skepticism.md) treats. Skepticism is about *willingness*: a reader who understands CGP but doubts it is worth the complexity. A technical barrier is about *ability*: a reader who would happily use CGP but cannot follow the explanation, because it assumes a concept they never absorbed. A piece of writing has to clear both, and confusing them is a common failure — answering an ability problem with a persuasion argument leaves the reader no better able to read the next line. - -The encouraging fact is that CGP's own design already lowers most of these barriers, because its ergonomic constructs were introduced precisely to let a reader use a capability without first understanding the harder concept beneath it. That turns much of the communication job into *using* those affordances well: teaching the forms that look like ordinary Rust first, and revealing the machinery only when a reader has a reason to want it. And doing so is not a simplification that misleads — the ergonomic forms are the *recommended* way to write CGP, so leading with them shows the idiom rather than a beginner's dialect the reader must later unlearn. - -The single most important teaching move that follows is **progressive disclosure**: reveal complexity only as a reader needs it, lead with the vanilla-looking forms, and defer the underlying machinery until it is asked for. The [reader profiles](reader-profiles.md) make this concrete — the newcomer should see only the on-ramp, while the advanced reader is handed the internals directly — and every barrier below is eased by the same discipline applied to a specific concept. - -## The prerequisite ladder - -The barriers form a ladder of prerequisite Rust knowledge, from the concepts most readers lack to the ones only advanced readers hold, and knowing where a reader stands on it decides how much a piece can safely show. Near the bottom sit **generic parameters** and **traits with bounds**, which a surprising share of working Rust developers are uneasy with; above them come **blanket implementations** and **associated types**, which many have used but few reason about fluently; and near the top sit **coherence and the orphan rule** and **type-level programming with higher-ranked trait bounds**, which only the advanced audience holds. The recurring mistake is to assume the reader stands higher on this ladder than they do, because CGP's authors and its most enthusiastic early readers stand near the top and forget how much of the audience does not. The rest of this document takes the load-bearing barriers in turn, names the design affordance that hides each, and gives the teaching move that eases it. - -## Generic parameters are intimidating - -The largest barrier by far is that many Rust developers are uncomfortable with generic parameters and disengage the moment they see a signature bristling with `` and bounds. Written naively, CGP is generic-heavy — provider traits carry an explicit context parameter, methods thread `Code` and `Input` type parameters — so a reader who bounces on generics bounces on CGP immediately, before any of its value has a chance to land. - -CGP's design answers this with a set of constructs that let a reader write and read real CGP with no generic parameter in sight. [`#[cgp_fn]`](../reference/macros/cgp_fn.md) turns a capability into a plain function whose simplest form shows no generics at all; [`#[cgp_impl]`](../reference/macros/cgp_impl.md) lets a provider omit the explicit context parameter and the `for Context` clause; and [`#[implicit]`](../reference/attributes/implicit.md) makes the values a provider draws from its context look like ordinary function arguments rather than generic bounds. The teaching move is to lead every introduction with these forms, introduce a generic parameter only at the first moment it earns its place, and never open with a raw provider-trait signature. Show the reader that CGP code can look like the Rust they already write, rather than reassuring them that the generics are not as scary as they look — the demonstration persuades where the reassurance does not. - -## The provider trait reads inside-out - -A distinct and disorienting barrier is that a provider trait, in its raw form, turns Rust's conventions inside-out: the original `Self` moves to a zero-sized provider marker, the context becomes an explicit type parameter, and so `self` and `Self` no longer mean what a Rust programmer's instinct says they mean. A reader meeting this cold has to hold an inversion in their head before they can read a single provider body, and it is one of the most common places understanding stalls. - -[`#[cgp_impl]`](../reference/macros/cgp_impl.md) exists to remove this barrier: it keeps `self` and `Self` meaning the context, lets the author omit the `for Context` clause, and puts the provider name in the attribute rather than the `Self` position, so a provider reads like an ordinary trait impl. The teaching move is to present `#[cgp_impl]` as *the* way to write a provider and to keep the inside-out [`#[cgp_provider]`](../reference/macros/cgp_provider.md) shape out of introductory material entirely; when the raw form must be shown to explain the internals, say plainly and up front that here `self` and `Self` are the context. Introducing the [consumer/provider split](../concepts/consumer-and-provider-traits.md) by leading with the raw provider trait is the surest way to lose a reader who would have followed the `#[cgp_impl]` form without trouble. - -## Traits and bounds on the context are a barrier of their own - -Below the provider inversion sits a plainer barrier: some readers are shaky on traits at all, and even those who are fluent find a `where Self: SomeTrait` bound unusual, because bounding the implementing type is uncommon in everyday Rust and reads as machinery rather than intent. A reader who cannot comfortably parse "this code requires the context to implement this trait" cannot follow how a provider states what it needs. - -Two constructs lower this. [`#[cgp_fn]`](../reference/macros/cgp_fn.md) needs no trait at all — the reader writes a function and never encounters a trait definition — which is why it is the gentlest possible entry point. And [`#[uses]`](../reference/attributes/uses.md) turns a `Self: Trait` dependency into a line that reads like a `use` import of a capability, so the reader declares what a provider depends on without confronting the fact that it is a bound on the generic context. The teaching move is to start the least experienced reader on `#[cgp_fn]` with no mention of traits, and to introduce `#[uses]` as "importing a capability the code relies on," deferring the underlying `where`-clause reality until the reader is ready to care about it. - -## Traits that are "magically" implemented, and their imports - -A subtler barrier appears once a reader meets a getter trait: [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) generates a blanket implementation, so the trait is satisfied for a context without the reader writing any impl, and the method it adds is callable only when the trait is in scope. Both halves confuse a newcomer — a trait that implements itself by unseen machinery, and a method that exists only after an import they did not know to add — and together they make the reader feel the system is doing things behind their back. - -[`#[implicit]`](../reference/attributes/implicit.md) sidesteps the whole tangle: a value is read from a context field as an ordinary function argument, with no getter trait to define, no blanket impl to reason about, and nothing to import. The teaching move is to make implicit arguments the default way a reader learns to pull values from a context, and to introduce getter traits only for the narrow cases an implicit argument cannot reach — a field on another type, a named shared capability — and only once the reader is past the intimidation stage. Leading with `#[cgp_auto_getter]` forces the "how is this implemented, and why can't I call it" question at exactly the wrong moment. - -## Associated types and qualified paths - -Abstract types raise a comprehension barrier through their syntax as much as their concept: referring to a context's abstract error means writing `Self::Error`, or in full `::Error`, and associated-type paths of that shape intimidate readers and clutter the code they appear in. A reader who is still unsure about generics is not ready to parse a fully-qualified associated-type projection in a return type. - -[`#[use_type]`](../reference/attributes/use_type.md) removes this by letting the reader import an abstract type and then write the bare name — `Error`, `Scalar` — with the qualified path filled in and the supertrait or bound added automatically. It reads like a `use` for a type, which is a mental model the reader already owns. The teaching move is to present `#[use_type]` exactly that way, as importing a type, and to keep hand-written `Self::`-qualified associated-type syntax out of introductory material, bringing it in only when explaining a construct's own local associated type, where it genuinely belongs. As with `#[uses]`, the value is that the reader states a dependency on a type without first having to understand that it is a bound on a generic context. - -## The wiring table and its machinery - -Wiring raises a barrier of abstraction: the mental model of a type-level lookup table, the [`DelegateComponent`](../reference/traits/delegate_component.md) trait, and the component-name markers are all machinery a reader must otherwise absorb before they can believe a context "has" a capability. Shown the underlying traits, a newcomer sees an intimidating type-level construction where they expected a simple choice. - -The design keeps the surface simple — a [`delegate_components!`](../reference/macros/delegate_components.md) table is a compact list matching each capability to the implementation that supplies it, and [`delegate_and_check_components!`](../reference/macros/delegate_and_check_components.md) folds in the verification so a beginner cannot forget it. The teaching move is to describe the table with a plain analogy — a settings map, a lookup table pairing each component with a chosen provider — and to keep `DelegateComponent` and [`IsProviderFor`](../reference/traits/is_provider_for.md) out of the beginner's view entirely, since they are the mechanism, not the model. Mentioning that the table is resolved at compile time and compiles away doubles as the reassurance that this abstraction has no runtime cost. - -## Reading the error messages - -A barrier that design can lower but not remove is the error message: a mis-wired context can produce a wall of generated types, and understanding that wall requires exactly the machinery a beginner is trying to avoid. This is where a reader who was following comfortably can be thrown, because the failure speaks in a register the ergonomic surface had spared them. - -CGP's mitigations are real, and one of them is now a dedicated tool. The oldest is [`check_components!`](../reference/macros/check_components.md), which forces the failure to surface at the wiring site and names the actual missing dependency — a field, a type, a capability — rather than letting it erupt far away; the [errors catalog](../errors/README.md) documents the recurring shapes. The newest, and the one that most lowers this barrier, is [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's error toolchain: run with `cargo cgp check` in place of `cargo check`, it turns on Rust's next-generation trait solver to **un-hide** the dependency errors the default solver suppresses — recovering the root cause of the very worst class, where plain `cargo check` reports only that a method's bounds are unsatisfied and never names the missing field — and then rewrites the classes it recognizes to lead with that cause, tagging each with a `[CGP-Exxx]` code. - -The teaching move is to hand the reader the tool early and teach them to read the root cause first, while setting the expectation with full honesty. Say that CGP's raw diagnostics can be verbose, that a check localizes them, and that `cargo cgp check` reshapes the recognized classes to lead with the cause — but that the tool is a v0.1.0-alpha that reshapes the core wiring errors rather than every class, leaving some (orphan-rule errors, for one) passing through as the compiler wrote them. The honest frame is that the error experience is dramatically better and actively improving, not solved. This is the one barrier where pretending it away costs more trust than admitting it, which is why [skepticism.md](skepticism.md) treats the same point from the persuasion side; the reference [`cargo-cgp.md`](../reference/cargo-cgp.md) covers installing and running the tool. - -## Knowing where to start, and why it is worth it - -Two meta-barriers sit above the individual constructs. The first is breadth: CGP has many pieces, and a reader who cannot see the minimal path assumes they must learn all of it before writing anything, which is its own reason to give up. The [modularity hierarchy](../concepts/modularity-hierarchy.md) is the answer to hand them — start at the lowest rung that solves the problem, a plain function or blanket trait before a full component — and a piece should give an explicit on-ramp rather than a tour of the whole surface. The second is motivation: a reader who can parse the syntax may still not grasp *why* the consumer/provider split earns its keep, so the mechanics read as ceremony. The move there is to lead with a concrete problem the reader has felt — a mock swapped for a real implementation, the orphan-rule newtype dance, a trait grown monolithic — and to defer the coherence theory that explains the split at a deeper level, exactly as [tag-lines.md](tag-lines.md) warns against opening on [coherence](../concepts/coherence.md). - -## The teaching strategy this points to - -The barriers above share one strategy, and a writer can apply it as a short checklist before publishing anything instructional. The principles reinforce each other, so the list is framed as a single discipline rather than independent tips: - -- **Disclose progressively.** Lead with the forms that look like ordinary Rust — `#[cgp_fn]`, `#[cgp_impl]`, `#[implicit]`, `#[uses]`, `#[use_type]` — and reveal the machinery beneath them only when a reader needs it, matching the depth to the reader profile. -- **Teach the ergonomic idioms as the idiom.** They are the recommended way to write CGP, not a simplified dialect, so leading with them is honest as well as gentle; keep the explicit and legacy forms for explaining internals and reading existing code. -- **Motivate before mechanism.** Open on a concrete problem the reader already has, not on the consumer/provider split or coherence; the mechanism lands only after the reader wants it. -- **Introduce vocabulary gradually and by analogy.** Defer the intimidating words — generic, blanket impl, coherence, higher-ranked trait bound, monomorphization — until they are needed, and anchor the ones you do use to a familiar picture, such as a wiring table as a settings map. -- **Show rather than reassure.** Demonstrating that CGP code can look like plain Rust convinces a nervous reader; telling them "it's not that hard" does not. -- **Be honest about the barrier design cannot fully remove — and point at the tooling that now lowers it.** Set expectations for verbose raw diagnostics, point at [`check_components!`](../reference/macros/check_components.md) and at `cargo cgp check`, which un-hides and leads with the root cause for the classes it recognizes, and concede that the tool is a young pre-release that does not yet reshape every class — rather than implying the diagnostics are as smooth as the surface syntax. diff --git a/docs/communication-strategy/vocabulary.md b/docs/communication-strategy/vocabulary.md deleted file mode 100644 index ebd840b3..00000000 --- a/docs/communication-strategy/vocabulary.md +++ /dev/null @@ -1,56 +0,0 @@ -# Vocabulary and message discipline - -This document is the canonical word list for public writing about CGP: the term to use for each concept, the plain-language gloss that introduces it to a cold reader, and the words and framings to avoid — so that everything written about CGP reads as one voice. - -## Why a shared word list matters - -CGP's public writing succeeds or fails on wording as much as on substance, so the section's "one voice" goal needs a single place that fixes the words rather than leaving each document to choose its own. The other documents each carry their own "say it like this / avoid this" guidance for their topic; this document consolidates those choices into a reference a writer checks before publishing, and it is the authority the others defer to — when a phrasing rule here and a phrasing rule elsewhere disagree, this document is where the disagreement is resolved. It is deliberately narrow: it governs *which word* to use, not *what to pitch* (that is [selling-points.md](selling-points.md)) or *how to answer an objection* (that is [skepticism.md](skepticism.md)). - -The list divides into three parts, and the division is the whole method. There are terms to **use** — the established CGP vocabulary, each paired with the one-line gloss that makes it legible to a reader meeting it for the first time. There are terms to **defer** — accurate internal words that intimidate a newcomer and should be withheld or introduced by analogy until the reader is ready, per [technical-barriers.md](technical-barriers.md). And there are words and framings to **avoid** — the ones that reliably trigger the misreadings [skepticism.md](skepticism.md) exists to prevent. A writer keeps the code and construct names accurate throughout, because a factual slip is shown to the audience most able to catch it, but chooses which of the accurate terms a given reader is ready for. - -## Terms to use, and how to introduce each - -The established CGP vocabulary is the same in public writing as in the rest of the knowledge base, so a reader moving between a blog post and the reference never reconciles two dialects. What public writing adds is the gloss: a term should be introduced with a plain-language definition on first use, then used consistently. Prefer these terms and these introductions. - -- **Context** — the concrete type an application wires and calls methods on. Introduce it as "the type that owns the wiring — your application, your test harness, your service," not as a bare "`Self`," because a cold reader has no reason to know the two coincide. -- **Component** — one capability, defined once, that can have many implementations. Introduce it as "an interface you can wire an implementation for," and reserve the internal detail that a component is a consumer trait plus a provider trait for when the reader asks how it works. -- **Consumer trait** and **provider trait** — the trait you *call* and the trait you *implement*. Introduce the pair only when a piece goes past the surface; for an introductory audience, "the trait you use" and "the code that implements it" carry the idea without the vocabulary. -- **Provider** — a named, swappable implementation of a component. Introduce it as "one implementation you can choose," which is the word's whole job; avoid explaining that it is a zero-sized marker type until the reader is reading generated code. -- **Wiring** — choosing which provider implements each component for a context. Introduce it as "a small table that says which implementation to use," and lean on the table image, since it is the single most load-bearing analogy in CGP writing. -- **Impl-side dependency** — a requirement a provider states in its own implementation rather than in the interface callers see. Introduce it as "the provider declares what it needs, and callers never see it," because the encapsulation benefit is the point and the phrase "impl-side" means nothing cold. -- **Pluggable trait implementations** — the chosen lead descriptor for the core capability, from [tag-lines.md](tag-lines.md): one interface with many interchangeable implementations, each chosen per context. Prefer "pluggable" as the novelty word and pair it with "at compile-time," because the tension between the two — pluggability a Rust reader expects at runtime, delivered statically — is the pitch in miniature. -- **Context-generic programming** — the name of the paradigm, never the pitch. Introduce it only after a concrete capability has landed, always paired with a plain descriptor, per [tag-lines.md](tag-lines.md). -- **`cargo-cgp`** — CGP's error toolchain, the cargo subcommand that reshapes CGP's compiler errors. Refer to it as "cargo-cgp, CGP's error toolchain" and to its use as "the `cargo cgp check` command" or "running `cargo cgp check`." Introduce what it does as "a dedicated checker that leads with the root cause" — it un-hides the buried cause and names the missing field instead of printing a wall of generated types. Prefer the framing **readable, root-cause-first, dramatically better and actively improving**, and always concede in the same breath that it is a young pre-release (**v0.1.0-alpha**) reshaping the core wiring errors but not yet every class. Never say CGP's errors are **"solved," "fixed,"** or **"as clear as any other Rust error's"** — the tool reshapes the recognized classes and concedes the rest, and this audience will install it and catch an overclaim within the hour. The facts live in the reference [`cargo-cgp.md`](../reference/cargo-cgp.md) and the [error catalog](../errors/README.md). - -## Terms to defer, and how to reveal them - -Some accurate terms are barriers, not vocabulary, for a reader below them on the [prerequisite ladder](technical-barriers.md), and leading with one loses the reader before the value lands. Defer these, and reveal each only when the reader has a reason to want it — matching the depth to the profile, so the advanced reader gets the real word immediately and the newcomer meets it late or by analogy. - -- **Coherence** and the **orphan rule** — the reason the consumer/provider split exists, but a theory the reader does not need to adopt CGP. Defer them behind the concrete pain (implementing a trait for a type you don't own), and introduce them, when at all, through that pain rather than as a rule to learn; a piece should never open on coherence, as [tag-lines.md](tag-lines.md) warns. -- **Blanket implementation**, **monomorphization**, **higher-ranked trait bound**, **`PhantomData`** — the machinery under the ergonomic surface. Withhold them from introductory material; when the runtime-cost question makes monomorphization worth naming, introduce it as "the compiler generates a direct call," not as jargon. -- **`DelegateComponent`**, **`IsProviderFor`**, the **type-level table** — the mechanism, not the model. Keep them out of a beginner's view entirely and describe the table with the settings-map analogy; bring the real traits in only for a reader reading an expansion or a compiler error, per [technical-barriers.md](technical-barriers.md). - -## Words and framings to avoid - -A handful of words reliably create the misunderstandings the section spends its effort preventing, and they should be avoided by default, each with a truer replacement that is also more persuasive to this audience. The through-line is that vague or grand wording lets the reader supply the worst reading, while a precise, smaller claim forecloses it and survives scrutiny — which, with the Rust audience, persuades *because* it survives. The pairs below consolidate the guidance scattered across the section into one checklist. - -- Avoid **"magic"**, even admiringly — this audience reads it as a warning. Say **"explicit,"** and point at the wiring table and the declared dependencies. -- Avoid **"automatically resolves," "finds," or "figures out"** the implementation — the single most common misframing, and the one that undercuts the coherence-freedom story. Say the provider is **"named explicitly, in one readable place."** -- Avoid flatly calling CGP **"a DI framework," "a reflection system,"** or **"an effect system"** — each makes the reader expect runtime behavior CGP does not have. Qualify each — **"compile-time, reflection-free dependency injection,"** **"compile-time structural reflection,"** **"the dynamic-binding fragment of effect handlers"** — and state the distinguishing limit in the same breath. -- Avoid implying **any runtime component**, even "a fast one." Say the wiring is **"resolved at compile time and compiled to a direct call."** -- Avoid **"zero-cost abstraction"** as a lead — it is accurate but worn. Prefer the concrete **"compiled to a direct call, with no vtable and nothing in the binary for a provider you don't use."** -- Avoid **"blazingly fast"** and any **unbenchmarked "faster than."** Say **"there is no runtime cost to compare,"** which is the honest and stronger claim. -- Avoid **"no boilerplate."** Say CGP **"moves the wiring into one readable place,"** because there is wiring and the reader will find it. -- Avoid **"replaces traits"** or **"a new language."** Say **"a superset of ordinary traits"** and **"a library on stable Rust," "an extension,"** never "a language of its own," per [tag-lines.md](tag-lines.md). -- Avoid **"reusable"** as the word for the novelty — to a Rust reader traits are already the reuse mechanism, so "reusable trait implementations" names nothing new and points at the wrong axis. Say **"pluggable"** (or "interchangeable," "many implementations chosen per context"), which names the capability coherence normally forbids, per [tag-lines.md](tag-lines.md). -- Avoid **"just"** in a competitor's description — "just macros," "just another DI framework" are the reader's dismissals, not ours, and echoing them concedes the frame. -- Avoid overstating maturity — **"works on stable Rust today"** is true and worth saying, while **"production-proven at scale"** needs evidence the [evaluator](reader-profiles.md) will notice is missing. -- Avoid calling CGP's errors **"solved," "fixed,"** or **"now as clear as any other Rust error's."** Say a dedicated checker — **`cargo cgp check`** — **"leads with the root cause"** for the classes it recognizes, and concede it is a **v0.1.0-alpha** that does not yet reshape them all: **"dramatically better and actively improving,"** not done. - -## The name, and the community's bridge terms - -The project's own name needs its own rule, because it is the term most likely to be misused as a pitch. Never ship **"context-generic programming"** as a standalone hook: it is opaque on first contact, and this is observed rather than hypothetical — readers in CGP's own release discussions said the phrase obscures more than it conveys and reached instead for "structural typing" or "duck typing for statically-typed code" to name what they thought it was ([attention-and-engagement.md](attention-and-engagement.md)). Always pair the name with a plain descriptor, and treat those community-supplied phrases as *bridge terms* for body copy — useful for meeting a reader where they are, but qualified, because CGP is nominal-and-wired rather than truly structural, and a precise reader will catch an unqualified "structural typing." Keep the name as the owned category term, per [tag-lines.md](tag-lines.md), and let a concrete capability carry the hook. - -## Keeping the list in sync - -Because this document consolidates wording rules the other documents also carry, it must stay consistent with them, and the coupling runs both ways. When a phrasing rule changes here, check the "say it like this / avoid this" lists in [selling-points.md](selling-points.md) and [skepticism.md](skepticism.md) and the title rules in [key-features.md](key-features.md) and [tag-lines.md](tag-lines.md), because a writer told to prefer a phrase in one place must never be warned against it in another. And when a CGP construct is renamed or a capability changes, the terms here are bound by the synchronization rule exactly as a reference document is: verify each against the source and the `/cgp` skill, and prefer the modern idioms the skill and the [guides](../guides/README.md) teach over the legacy forms a reader will still meet in older code. diff --git a/docs/communication-strategy/worked-examples.md b/docs/communication-strategy/worked-examples.md deleted file mode 100644 index abdb4f1a..00000000 --- a/docs/communication-strategy/worked-examples.md +++ /dev/null @@ -1,171 +0,0 @@ -# Worked examples: annotated model drafts - -This document assembles the section's guidance into finished pieces of public writing — a launch post, a README above the fold, a social thread — and annotates each so a writer can see every decision and trace it to the document that argues for it. - -## How to read these drafts - -These drafts exist because the rest of the section is prescriptive and abstract, and a marketing-naive expert learns faster from one finished model than from twelve documents of rules. Each is a model of one artifact from [formats.md](formats.md), written the way that format's playbook prescribes, then followed by a note that names each move and its source. Read the draft first as a reader would, front to back and fast; then read the annotations to see the machinery behind it. The point is not to copy the copy but to watch the whole apparatus operate at once, so you can run it yourself on your own piece. - -Treat every draft as an illustration, not approved copy, because two honesty caveats travel with each one. The tag line they use is the chosen one from [tag-lines.md](tag-lines.md); it is settled for consistency's sake, but publication remains the real measurement of any hook, so treat the copy as a strong starting point rather than a guarantee — [attention-and-engagement.md](attention-and-engagement.md) explains why. And every CGP claim and code snippet is bound by the knowledge base's synchronization rule exactly as a selling point is: verify it against the source and the `/cgp` skill before it ships, because a stale claim in a model draft is copied straight into real copy. - -The code uses the modern idioms the `/cgp` skill teaches, so a reader who borrows a draft learns the current idiom rather than a dialect to unlearn. A provider is written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), a value is read with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) or an [`#[implicit]`](../reference/attributes/implicit.md) argument, and wiring is a [`delegate_components!`](../reference/macros/delegate_components.md) table — the same forms [problems-solved.md](problems-solved.md) uses, and for the same reason. - -## The link-aggregator launch post - -The launch post on Lobsters or the Rust subreddit is the format to get right first, because it is where CGP is actually discussed and its reader is the pragmatic skimmer who forms and broadcasts a snap judgment ([attention-and-engagement.md](attention-and-engagement.md)). The draft below leads with a pain, shows the workaround the reader already knows, and concedes the boundary in the same breath, per the [launch-post playbook](formats.md). - -### The draft - -> **Pluggable trait implementations for Rust, at compile-time** -> -> Every Rust project eventually needs to swap a real implementation for a fake one — the real email sender in production, a recording stub in tests. The usual routes each cost something. A `Box` pays for dynamic dispatch and spreads a trait object through your types. A generic `` parameter has to be threaded through every layer that touches it, and it multiplies as more dependencies join. A dependency-injection crate brings machinery most of us would rather not. -> -> CGP makes the swap a single line. You define the capability once: - -```rust -#[cgp_component(EmailSender)] -pub trait CanSendEmail { - fn send_email(&self, to: &str, body: &str); -} -``` - -> write as many implementations as you need, each an ordinary-looking impl: - -```rust -#[cgp_impl(new SendViaSmtp)] -impl EmailSender { /* connect and send over SMTP */ } - -#[cgp_impl(new RecordEmails)] -impl EmailSender { /* record each message so a test can assert on it */ } -``` - -> and let each context pick the one it wants: - -```rust -delegate_components! { App { EmailSenderComponent: SendViaSmtp } } -delegate_components! { TestApp { EmailSenderComponent: RecordEmails } } -``` - -> `App` sends real mail; `TestApp` records it. Neither pays for `dyn`, the choice is one greppable line, and the code that calls `self.send_email(..)` never changes. -> -> To be clear about the cost: this is more than a single trait needs. For a dependency with one implementation, a plain trait is still the right tool, and CGP would be over-engineering. It earns its keep when the implementations multiply, when the choice must differ per context, or when you're implementing a trait for a type you don't own and the orphan rule blocks you. All the wiring is resolved at compile time and compiled to direct calls — there is no runtime container, no reflection, and nothing left in the binary for a provider you don't use. -> -> It's a library on stable Rust, and it's a superset of ordinary traits, so you can use it in one corner and leave the rest of your code unchanged. -> -> Quickstart: [link] - -### How the draft applies its guidance - -Every choice above is a rule from elsewhere in the section, and naming them shows how to reproduce the result rather than the wording. The moves, in reading order: - -- **The title is a concrete capability, not the paradigm name.** "Pluggable trait implementations for Rust, at compile-time" is the concrete-capability half of the chosen tag line in [tag-lines.md](tag-lines.md), used over "context-generic programming" because the title is the whole pitch for most of this audience, per the [launch-post playbook](formats.md). -- **It opens on a pain, not the paradigm.** The mock-in-tests swap is the most universal problem CGP removes and the entry to lead with for the working developer, drawn straight from [problems-solved.md](problems-solved.md) and keyed to that [reader profile](reader-profiles.md). -- **It shows the workarounds the reader already writes.** Naming `Box`, the threaded generic, and the DI crate lets the CGP version arrive as relief rather than as a new thing to learn — the before/after discipline of [problems-solved.md](problems-solved.md). -- **It concedes the cost and the boundary in plain words.** "This is more than a single trait needs … CGP would be over-engineering" defuses the "why not just traits" and "over-engineered" reflexes from [skepticism.md](skepticism.md) by drawing the line [positioning.md](positioning.md) draws — and the concession is what makes the rest believable to this audience. -- **It states the zero runtime cost precisely.** "Resolved at compile time … no runtime container, no reflection, nothing left in the binary" is the [zero-runtime-cost selling point](selling-points.md), worded to avoid implying any runtime component, per the [vocabulary](vocabulary.md) avoid-list. -- **It calls the wiring "one greppable line."** That answers the "which code actually runs" traceability worry from [skepticism.md](skepticism.md) before it is raised. -- **It closes on stable Rust and gradual adoption.** "A library on stable Rust … a superset of ordinary traits" pairs two selling points that lower the evaluator's adoption risk ([selling-points.md](selling-points.md), the "Still Ordinary Rust" [key feature](key-features.md)). -- **The call to action is the quickstart, not the paradigm.** The skimmer is ready only for a low-commitment look, so the [conversion ladder](formats.md) asks for that and nothing heavier. - -It is as notable for what it avoids: no paradigm name in the opener, no "magic," no "automatically resolves," and no unqualified "DI framework" — each an entry on the [vocabulary](vocabulary.md) avoid-list because each invites a dismissal. - -## The README above the fold - -The README's first screen is where an evaluator and a skimmer both decide in seconds whether to continue, so it must carry the tag line, the headline features, and runnable code before the reader scrolls ([formats.md](formats.md)). The draft models only the part above the fold — the layout term the [glossary](glossary.md) defines — because that is where the decision is made. - -### The draft - -> **Context-Generic Programming** -> -> *A language extension for Rust, with pluggable trait implementations at compile-time.* -> -> Still ordinary Rust — no nightly, no fork, and zero runtime cost. `cargo add cgp` -> -> --- -> -> **One Interface, Many Implementations.** Write many interchangeable implementations of the same interface and choose between them per context, with the overlapping and orphan implementations Rust normally forbids made safe because every choice is explicit and local. -> -> **Zero-Cost Abstraction.** Everything is resolved at compile time and compiles down to direct calls, so the flexibility costs nothing at runtime and unused providers never reach the binary. -> -> **Still Ordinary Rust.** A superset of ordinary traits you adopt one piece at a time: providers read like normal impls and implicit arguments like normal parameters, so a codebase can use it in one corner and stay otherwise vanilla. -> -> *(The full headline set is in [key-features.md](key-features.md).)* - -```rust -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self) -> String; -} - -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[cgp_impl(new GreetHello)] -#[uses(HasName)] -impl Greeter { - fn greet(&self) -> String { - format!("Hello, {}!", self.name()) - } -} - -#[derive(HasField)] -pub struct Person { - pub name: String, -} - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} - -fn main() { - let person = Person { name: "World".to_owned() }; - println!("{}", person.greet()); // Hello, World! -} -``` - -> **[Quickstart →]** · **[Is it ready for production? — honest maturity notes →]** - -### How the draft applies its guidance - -The README makes different promises to two readers at once, and each element serves one or both. The moves: - -- **The tag line is layered.** The owned name as the title, the chosen descriptor beneath it, and a reassurance line under that are the layered form from [tag-lines.md](tag-lines.md); the reassurance does the skeptic's work by saying "still ordinary Rust — no nightly, zero runtime cost" before the reader supplies a worse reading of "language extension" or "pluggable." -- **It states "a library on stable Rust" and the install line early.** The [evaluator](reader-profiles.md) is scanning for the toolchain gamble, so the reassurance and `cargo add cgp` come before the prose, per [formats.md](formats.md) and the stable-Rust [selling point](selling-points.md). -- **The feature set is the ruthless few, and each title avoids a repellent word.** Three of the canonical headline features from [key-features.md](key-features.md) appear, each title leading with a concrete capability or a recognized Rust term rather than "modular," "macros," or "magic," and each sentence carrying its honest qualifier ("at compile time," "made safe because … explicit and local"). -- **Runnable code sits above the fold.** A reader wants to see the code before the prose, and CGP's own launch feedback asked for exactly a runnable example ([attention-and-engagement.md](attention-and-engagement.md)); the Hello World shows a component, a provider, a getter, and wiring in the smallest honest form. -- **There are two calls to action, matched to the two readers.** The quickstart is the skimmer's rung and the maturity discussion is the evaluator's, straight from the [conversion ladder](formats.md). - -It also leaves out, deliberately, any feature titled "Dependency Injection": on a general front page that label imports the runtime-framework baggage catalogued in [skepticism.md](skepticism.md), so the DI value ships through "Type-Safe Wiring" and "Abstract Over Every Dependency" instead, as [key-features.md](key-features.md) argues. - -## The social thread - -A thread on Mastodon, Bluesky, or X has seconds of attention and one job — earn the click without inviting the dunk — so it leads with a single concrete pain, keeps jargon out of the first post, and lets one link carry the depth ([formats.md](formats.md)). The draft is three posts. - -### The draft - -> **1/** Swapping a real implementation for a mock in Rust usually means a `Box` you pay for at runtime, or a generic type parameter you thread through every layer of your code. There's a third option that costs neither. 🧵 - -> **2/** Define the capability once, write as many implementations as you like, and let each context — your app, your test harness — pick one in a single line. The call site never changes, and it compiles down to a direct call. No trait objects, no runtime dispatch. - -> **3/** It's a library on stable Rust and a superset of ordinary traits, so you can try it in one corner without rewriting anything. And if a dependency has just one implementation, keep using a plain trait — this earns its keep once the implementations multiply. [link] - -### How the draft applies its guidance - -A thread fails in two opposite ways — too jargon-heavy to parse, or so compressed it reads as an overclaim — and the draft is shaped to avoid both. The moves: - -- **The first post is one concrete pain, with no jargon and no paradigm name.** It opens on the mock swap from [problems-solved.md](problems-solved.md), because the [first-contact skimmer](reader-profiles.md) gives the piece three seconds and pattern-matches anything abstract to a category they already dismiss. -- **It earns the click without inviting the dunk.** Conceding "keep using a plain trait" in the last post is the [positioning.md](positioning.md) move that disarms the "over-engineered" reflex ([skepticism.md](skepticism.md)); a thread that only sells is the one a technical crowd piles onto. -- **The mechanism is stated precisely, not hyped.** "Compiles down to a direct call" is the [vocabulary](vocabulary.md)-approved phrasing, chosen over "blazingly fast," which would read as the exact overclaim the skimmer broadcasts. -- **One link carries the depth, and it is the whole call to action.** A thread is a hook-delivery mechanism, not a place to explain, per [formats.md](formats.md). - -## Adapting these to your own piece - -These three are templates for the moves, not for the words, so adapting one to a real piece means re-running the decisions rather than find-and-replacing the copy. Start by naming the one or two dominant [reader profiles](reader-profiles.md) for your channel, then swap the anchor pain for the one that reader feels most sharply — the [orphan-rule escape](problems-solved.md) for the trait-heavy developer, the [error-type swap](problems-solved.md) for the systems reader, and so on — because the pain, not the tag line, is what decides whether the piece lands. - -Two things must survive every adaptation. Keep the conceded cost: it is load-bearing, not optional politeness, and dropping it is how a piece that earned attention loses trust ([skepticism.md](skepticism.md), [positioning.md](positioning.md)). And re-verify every CGP claim and code snippet against the source and the `/cgp` skill before publishing, since a draft copied without that check propagates whatever has gone stale in it. When you find a move these three drafts do not cover, the right next step is a new draft here — a talk opener, a comparison table, a `This Week in Rust` blurb — added in the same shape, so this document grows into the section's library of worked models rather than a fixed set of three. diff --git a/docs/concepts/README.md b/docs/concepts/README.md deleted file mode 100644 index aba7bbcd..00000000 --- a/docs/concepts/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# CGP Concepts - -This directory holds the high-level conceptual overviews that tie the CGP constructs together — the consumer/provider duality, dependency injection, namespaces, handlers, and so on. Each document explains one cross-cutting idea and points down into the [reference documents](../reference/README.md) for the per-construct mechanics, so a reader can grasp the shape of an idea here and follow the links for the precise semantics. - -## How concepts differ from reference documents and examples - -A concept document explains an *idea that spans several constructs*, whereas a [reference document](../reference/README.md) explains a *single construct completely* and an [example](../examples/README.md) develops a *single use case end to end*. A fourth section, [guides](../guides/README.md), is different again: a concept explains *what an idea is*, while a guide directs *which choice to make* when writing CGP code. These are complementary. The reference for `delegate_components!` states its syntax and exact expansion; the [coherence](coherence.md) concept explains *why* CGP wires components through such a table at all, and the [modular serialization](../examples/modular-serialization.md) example shows the pattern solving a real problem. A concept document carries only enough mechanism to make the idea legible and links to the reference for the rest, so a reader who wants the detail follows the link while a reader who wants the framing stays here. - -## The catalog - -The authoring rules for concept documents, including when a cross-cutting idea earns its own page, live in [../AGENTS.md](../AGENTS.md). These documents explain the ideas that connect the constructs, each linking down to the per-construct references for the detail. - -- [Bypassing coherence](coherence.md) — what Rust's coherence rules forbid, and the incoherent-impl-plus-local-wiring strategy CGP uses to work around them. -- [Modularity hierarchy](modularity-hierarchy.md) — the ladder from a single blanket impl to per-type-per-provider wiring, and how to pick the lowest rung a use case needs. -- [Consumer and provider traits](consumer-and-provider-traits.md) — the trait duality at the heart of CGP and how it sidesteps coherence. -- [Impl-side dependencies](impl-side-dependencies.md) — dependency injection through the `where` clause of blanket impls. -- [Implicit arguments](implicit-arguments.md) — writing providers as ordinary functions whose arguments come from context fields. -- [Higher-order providers](higher-order-providers.md) — providers parameterized by other providers. -- [Check traits](check-traits.md) — why wiring is lazy and how to verify it at compile time. -- [Abstract types](abstract-types.md) — abstract associated types shared and swapped across contexts. -- [Modular error handling](modular-error-handling.md) — an abstract error type plus raising and wrapping capabilities, with the error type and construction strategy chosen by wiring. -- [Aggregate providers](aggregate-providers.md) — bundling a group of component wirings into a reusable provider that contexts delegate to as a unit, and why such a bundle is a provider rather than a context. -- [Namespaces](namespaces.md) — reusable, inheritable wiring tables and preset-style configuration. -- [Handlers](handlers.md) — the Computer/Producer/Handler family of computation components and their sync/async/fallible/by-reference variants. -- [Extensible records](extensible-records.md) — building and reading a struct by its named fields, and the extensible builder pattern. -- [Extensible variants](extensible-variants.md) — constructing and deconstructing an enum by its named variants, and the extensible visitor pattern. -- [Dispatching](dispatching.md) — routing extensible-data inputs to per-field and per-variant handlers. -- [Monadic handlers](monadic-handlers.md) — composing handlers through the identity/ok/err monads. -- [Type-level DSLs](type-level-dsls.md) — encoding a small language as types and interpreting it at compile time through CGP wiring. -- [Recovering `Send` bounds](send-bounds.md) — restoring the `Send` guarantee an async trait method drops, as a stand-in for the Return Type Notation stable Rust lacks. diff --git a/docs/concepts/abstract-types.md b/docs/concepts/abstract-types.md deleted file mode 100644 index 82759749..00000000 --- a/docs/concepts/abstract-types.md +++ /dev/null @@ -1,85 +0,0 @@ -# Abstract types - -An abstract type is a trait carrying a single associated type that generic code refers to as `Self::Foo` without committing to any concrete type, so that the concrete choice is supplied by wiring and can differ from one context to another. - -## The idea - -An abstract type lets generic code name a type it does not fix. Instead of hard-coding `f64` or `String`, a CGP trait declares an associated type — `trait HasScalarType { type Scalar; }` — and code written against it refers to `Self::Scalar`, leaving the actual type open. The trait is the abstraction; the associated type is the slot a context fills in. This is the type-level analogue of dependency injection: just as a getter trait lets a context supply a *value* the provider needs, an abstract-type trait lets a context supply a *type* the provider builds on. - -The reason this matters is the same reason behavior is made swappable in CGP. A provider written in terms of `Self::Scalar` works unchanged whether a context chooses `f32`, `f64`, or a fixed-point type, and two contexts can make different choices from the same generic code. An abstract type is, in the end, nothing more than an ordinary Rust trait with one associated type — generic functions constrain `Context: HasScalarType` and use `Context::Scalar` exactly as they would any associated type. CGP adds machinery to make declaring and wiring these traits cheap, but the underlying construct is plain Rust, and a context can always implement the trait directly with `impl HasScalarType for App { type Scalar = f64; }`. - -## Making a type swappable with `#[cgp_type]` - -The [`#[cgp_type]`](../reference/macros/cgp_type.md) macro turns an abstract-type trait into a full CGP component, so the concrete type can be chosen through wiring rather than a hand-written impl. Applied to a trait with exactly one associated type, it produces everything [`#[cgp_component]`](../reference/macros/cgp_component.md) would — the consumer trait, the provider trait, the blanket impls, the component marker — but specialized to forward an associated type rather than a method: - -```rust -#[cgp_type] -pub trait HasScalarType { - type Scalar: Copy; -} -``` - -The default provider name is keyed off the *associated type* name, not the trait name, so `Scalar` yields `ScalarTypeProvider` and the component `ScalarTypeProviderComponent`. A bound on the associated type, such as `Copy` above, is carried everywhere the type appears in the expansion and enforced on whatever concrete type a context chooses. The decisive extra construct `#[cgp_type]` generates is a blanket impl for `UseType`, described next, which is what lets a context pick a concrete type without writing a provider of its own. - -## Wiring a concrete type with `UseType` - -A context binds an abstract type to a concrete one by wiring its provider component to [`UseType`](../reference/providers/use_type.md). Because every abstract-type provider has the same trivial shape — "the associated type *is* this concrete type" — `#[cgp_type]` generates that shape once as a blanket impl of the provider trait for `UseType`, setting the associated type to the generic parameter. A context then names the concrete type directly in its delegation table: - -```rust -use cgp::prelude::*; - -#[cgp_type] -pub trait HasScalarType { - type Scalar: Copy; -} - -pub struct App; - -delegate_components! { - App { - ScalarTypeProviderComponent: UseType, - } -} -``` - -Wiring `ScalarTypeProviderComponent` to `UseType` makes `App` implement `HasScalarType` with `Scalar = f64`, with no bespoke provider, and the `Copy` bound is checked against `f64` at the wiring site. This is the type-level mirror of how `UseField` supplies a value-level getter. The `UseType` *provider struct* should not be confused with the [`#[use_type]`](../reference/attributes/use_type.md) *attribute*: the provider, covered here, wires a concrete type into a context; the attribute imports an abstract type into a definition and rewrites bare mentions of it into fully-qualified form. They are complementary but different things. - -## Sharing a type across contexts - -The point of an abstract type compounds when several pieces of generic code share one. Because the type lives on a trait the context implements, every provider and trait that needs a `Scalar` refers to the *same* `Self::Scalar`, so a context fixes the choice once and all of them agree. This is most valuable when the main target of a trait is a generic parameter rather than the context itself — for example a `CanCalculateAreaOfShape` trait implemented by a common `Context` for many `Shape` types. The shapes need not each declare a scalar type or coordinate on a common one; the shared context supplies a single `Scalar`, and every shape interoperates through it: - -```rust -#[cgp_type] -pub trait HasScalarType { - type Scalar: Copy; -} - -#[cgp_component(AreaOfShapeCalculator)] -#[use_type(HasScalarType.Scalar)] -pub trait CanCalculateAreaOfShape { - fn area(&self, shape: &Shape) -> Scalar; -} -``` - -Here `Rectangle` and `Circle` carry no scalar type of their own; the context that calculates their areas does, via its `HasScalarType` wiring, and switching the context's `UseType` to `UseType` changes the scalar for every shape at once. The same arrangement is how CGP shares an error type across an entire application, which is the canonical example. - -## The canonical example: `HasErrorType` - -CGP's most-used abstract type is [`HasErrorType`](../reference/components/has_error_type.md), which supplies one `Error` type that an entire context's code agrees on. It is defined with `#[cgp_type]` exactly as above: - -```rust -#[cgp_type] -pub trait HasErrorType { - type Error: Debug; -} -``` - -A context wires `ErrorTypeProviderComponent` to `UseType` (or any concrete error), and from then on every fallible provider in that context refers to the same `Self::Error`, so errors compose without each component declaring its own error type or converting between several. The `Debug` bound lets that abstract error be unwrapped and logged. `HasErrorType` shows the pattern at its most useful: a single associated type, supplied once by the context through `UseType`, shared by arbitrarily much generic code — and built on by the further error machinery (`CanRaiseError`, `CanWrapError`) that constrains `Self: HasErrorType` and works against the abstract `Error` throughout. - -## Related constructs - -Abstract types are defined with [`#[cgp_type]`](../reference/macros/cgp_type.md), the abstract-type specialization of [`#[cgp_component]`](../reference/macros/cgp_component.md). They are built on CGP's foundational [`HasType`/`TypeProvider`](../reference/components/has_type.md) component, the built-in abstract-type machinery that `#[cgp_type]` adapts. A context binds a concrete type through the [`UseType` provider](../reference/providers/use_type.md), and other definitions import an abstract type and rewrite bare mentions of it with the [`#[use_type]` attribute](../reference/attributes/use_type.md) — a different construct from the provider despite the shared name. [`HasErrorType`](../reference/components/has_error_type.md) is the canonical abstract type, supplying a shared `Error` type across a context's code. Abstract-type components are wired with [`delegate_components!`](../reference/macros/delegate_components.md) and verified with [`check_components!`](../reference/macros/check_components.md) like any other component. - -## Source - -The runtime `HasType`, `TypeProvider`, and `UseType` definitions are in [crates/core/cgp-type/src/](../../crates/core/cgp-type/src/); `HasErrorType` is in [crates/core/cgp-error/src/traits/has_error_type.rs](../../crates/core/cgp-error/src/traits/has_error_type.rs); the `#[cgp_type]` codegen is in [crates/macros/cgp-macro-core/src/types/cgp_type/](../../crates/macros/cgp-macro-core/src/types/cgp_type/). diff --git a/docs/concepts/aggregate-providers.md b/docs/concepts/aggregate-providers.md deleted file mode 100644 index 1eeee528..00000000 --- a/docs/concepts/aggregate-providers.md +++ /dev/null @@ -1,64 +0,0 @@ -# Aggregate providers - -An aggregate provider is a zero-sized provider whose implementation is a delegation table rather than a method body: it dispatches each component to a sub-provider, so a group of component wirings can be bundled once and reused by many contexts as a single unit. - -## Wiring a table that is not a context - -The [`delegate_components!`](../reference/macros/delegate_components.md) macro usually defines the wiring of a concrete context — the type a caller holds and invokes methods on. But its target need not be a context at all. With a leading `new` keyword, the macro defines a fresh zero-sized struct and gives *it* a delegation table: - -```rust -delegate_components! { - new GeometryComponents { - AreaCalculatorComponent: RectangleArea, - PerimeterCalculatorComponent: RectanglePerimeter, - } -} -``` - -`GeometryComponents` is an **aggregate provider**: a marker type, never instantiated, whose only content is the [`DelegateComponent`](../reference/traits/delegate_component.md) table that maps `AreaCalculatorComponent` to `RectangleArea` and `PerimeterCalculatorComponent` to `RectanglePerimeter`. It aggregates a set of related component wirings under one name so they can be adopted together, the way a small library of behaviors is packaged for reuse. - -## How a context uses one - -A context adopts the whole bundle by delegating a group of components to the aggregate provider in one entry. Because the aggregate provider carries its own table for those components, the context does not repeat the individual wirings: - -```rust -delegate_components! { - Rectangle { - [AreaCalculatorComponent, PerimeterCalculatorComponent]: GeometryComponents, - } -} -``` - -After this, `Rectangle` implements both `CanCalculateArea` and `CanCalculatePerimeter`, and each call resolves through `Rectangle`'s table to `GeometryComponents`, then through `GeometryComponents`' table to the leaf provider. Swapping every context that uses the bundle to a different set of behaviors is a matter of editing the bundle once, not each context. This is the same benefit a [namespace](namespaces.md) gives, reached by a lighter mechanism: an aggregate provider is delegated to explicitly, component by component, rather than joined and inherited by path. - -## A provider, not a context - -The defining property of an aggregate provider — and the one that governs how it is checked — is that it is a **provider**, occupying the delegate position in a wiring, and is *never its own context*. Tracing a call makes this concrete. When `rect.area()` resolves, the context type stays `Rectangle` through the entire chain: - -- `Rectangle: CanCalculateArea` holds because `Rectangle: AreaCalculator` (the consumer blanket impl). -- `Rectangle: AreaCalculator` holds because `Rectangle` delegates `AreaCalculatorComponent` to `GeometryComponents` and `GeometryComponents: AreaCalculator` (the provider blanket impl). -- `GeometryComponents: AreaCalculator` holds because `GeometryComponents` delegates `AreaCalculatorComponent` to `RectangleArea` and `RectangleArea: AreaCalculator`. - -The context argument is `Rectangle` at every step; `GeometryComponents` appears only in the `Self`/delegate position, never as the `Context`. So a leaf provider like `RectangleArea` reads its fields and capabilities from `Rectangle`, the real context, not from the bundle. `GeometryComponents` has no fields and never implements a provider trait with itself in the context position — it is pure routing. This is why an aggregate provider is a *provider* in the exact sense of the [consumer/provider duality](consumer-and-provider-traits.md): it is something a context delegates *to*, not something that is used *as* a context. - -## Composing aggregate providers - -Aggregate providers nest, because a delegate is allowed to be another `DelegateComponent` carrier rather than a leaf. One bundle can delegate a component to a second bundle, which delegates it onward, and the resolution walks each table in turn — all while the context argument stays fixed on the real context at the end of the chain. The [`IsProviderFor`](../reference/traits/is_provider_for.md) forwarding impl that each `delegate_components!` entry generates chains the same way, so a dependency left unmet several bundles deep still propagates back to the context where the component is finally checked, rather than vanishing at a bundle boundary. Layering bundles this way is how a base set of behaviors is extended or specialized without rewriting the base. - -## Why an aggregate provider is never checked directly - -An aggregate provider must be wired with plain [`delegate_components!`](../reference/macros/delegate_components.md), never [`delegate_and_check_components!`](../reference/macros/delegate_and_check_components.md) — and this is a correctness rule, not merely a stylistic one. The check that `delegate_and_check_components!` derives asserts [`CanUseComponent`](../reference/traits/can_use_component.md) on the *target*, which asks whether the target can use each component *as a context*: whether the target both delegates the component and satisfies the chosen provider's real dependencies for itself. An aggregate provider satisfies neither half in a meaningful way — it is never its own context, has no fields, and never stands in the context position — so the assertion cannot hold, and the fused macro would report failures that describe a situation that never occurs at runtime. - -An aggregate provider is verified indirectly instead, at the place where it is actually used. Checking a real context that delegates a component to the bundle exercises the whole chain, because [`check_components!`](../reference/macros/check_components.md) on that context walks through the bundle's `IsProviderFor` forwarding down to the leaf provider's bounds. When a bundle needs verifying on its own — to localize which layer of a nested stack is broken — the `#[check_providers(...)]` form of `check_components!` asserts `IsProviderFor` directly on the named aggregate provider *for a concrete context*, which is the provider-side check the bundle's role calls for, rather than the context-side `CanUseComponent` check. Both routes verify the bundle through a real context; neither treats the bundle as a context itself. See [check traits](check-traits.md) for the mechanics of both check forms. - -## Aggregate providers versus namespaces - -An aggregate provider and a [namespace](namespaces.md) are the two ways CGP packages reusable wiring, and they differ in how a context adopts the package. A context adopts an aggregate provider by *delegating* named components to it explicitly — `[A, B]: TheBundle` — so the context spells out which components come from the bundle. A context adopts a namespace by *joining* it with a `namespace` header, inheriting every entry the namespace resolves and overriding only what it names. The aggregate provider is the more direct, table-to-table mechanism and is the natural choice for a small, explicitly-delegated bundle of behaviors; the namespace adds path-keyed lookup and inheritance-with-override for preset-style configuration that scales across many components. Both are resolved entirely at compile time through `DelegateComponent`, and both are providers-not-contexts in the checking sense. - -## Related constructs - -An aggregate provider is defined and consumed through [`delegate_components!`](../reference/macros/delegate_components.md), whose `new` keyword emits the bundle struct and table. Its table is the [`DelegateComponent`](../reference/traits/delegate_component.md) trait, and each entry also generates the [`IsProviderFor`](../reference/traits/is_provider_for.md) forwarding that keeps dependencies diagnosable across nested bundles. Because it is a provider and not a context, it is checked through a real context with [`check_components!`](../reference/macros/check_components.md) or its `#[check_providers(...)]` form rather than with [`delegate_and_check_components!`](../reference/macros/delegate_and_check_components.md); see [check traits](check-traits.md). It is the lighter sibling of the [namespace](namespaces.md) as a reusable-wiring mechanism, and it rests on the [consumer/provider duality](consumer-and-provider-traits.md) that distinguishes what is delegated to from what is used as a context. - -## Source - -The `new` keyword that defines a bundle struct alongside its table is handled in the delegation-table parser at [crates/macros/cgp-macro-core/src/types/delegate_component/](../../crates/macros/cgp-macro-core/src/types/delegate_component/) (the table and `new` handling in `table/main.rs`, the `DelegateComponent`/`IsProviderFor` impl construction in `mapping/eval.rs`). The `DelegateComponent` trait an aggregate provider carries is defined in [crates/core/cgp-component/src/traits/delegate_component.rs](../../crates/core/cgp-component/src/traits/delegate_component.rs), and the `IsProviderFor` marker whose forwarding chains through bundles is in [crates/core/cgp-component/src/traits/is_provider.rs](../../crates/core/cgp-component/src/traits/is_provider.rs). diff --git a/docs/concepts/check-traits.md b/docs/concepts/check-traits.md deleted file mode 100644 index 4330052b..00000000 --- a/docs/concepts/check-traits.md +++ /dev/null @@ -1,56 +0,0 @@ -# Check traits - -Check traits are compile-time-only assertions that a context's CGP wiring is complete, written to turn the vague errors that lazy wiring produces into readable ones that name the actual missing dependency. - -## Why CGP wiring is lazy - -CGP wiring is lazy: recording that a context delegates a component to a provider does not verify that the provider can actually satisfy that component. When a context's delegation table maps `AreaCalculatorComponent` to some provider, the type system accepts the [`DelegateComponent`](../reference/traits/delegate_component.md) entry on its own terms — it stores "this key points to this provider" as an associated type and asks no further questions. Whether that provider's own `where` bounds hold for this particular context is simply not checked at the point of delegation. The check is deferred until something downstream tries to *use* the component, which is the first moment the compiler is forced to evaluate the provider's bounds against the concrete context. - -This laziness is what makes CGP composable, but it has a cost: a context can look fully wired and still be broken. Every entry compiles, the struct compiles, the whole module compiles — and then the first call to a consumer trait method fails, often far from the wiring that caused it. A missing `name` field, a missing abstract type, an unsatisfied transitive bound three providers deep: none of these surface where the mistake was made. - -## Why the resulting errors are poor - -When a lazily-wired context is finally used and a dependency is missing, the compiler reports the outermost unmet conclusion and hides the reasoning behind it. Asking the plain question "does this context implement the consumer trait?" makes Rust answer with the last link in the chain — typically that the provider does not implement the provider trait for this context — without explaining *why* the provider's bounds were not met. The root cause, often a single missing getter or type, is buried beneath a conclusion that points at the provider rather than at the gap. Worse, the relevant type names are expanded into their full type-level forms (the verbose `Symbol<…, Chars<…>>` spelling of a field name, for instance), so even the surface error is hard to read. - -The fix is to force the compiler to evaluate the provider's bounds *and report them in detail*, at a location the author controls — the wiring site — rather than wherever the component happens to be used first. - -## How check traits force readable errors - -A check trait is a dummy trait whose supertrait is the requirement being asserted; implementing it for a context with an empty body compiles only if that requirement holds. The hand-written form is plain Rust: - -```rust -trait CanUsePerson: CanGreet {} -impl CanUsePerson for Person {} -``` - -The `impl` block has nothing to prove on its own, so it succeeds exactly when `Person: CanGreet` holds and fails to compile otherwise. Placed next to the wiring, it converts a latent gap into an immediate compile error at a known line. But asserting the consumer trait directly is not enough, because it reproduces the same vague error described above — it tells you the provider trait is not implemented without saying why. The remedy is to route the assertion through [`CanUseComponent`](../reference/traits/can_use_component.md) instead of the consumer trait. - -`CanUseComponent` is satisfied only when the context both delegates the component and the delegated provider satisfies [`IsProviderFor`](../reference/traits/is_provider_for.md) for that context. The crucial property is that `IsProviderFor` is generated to carry the provider's *real* `where` bounds — the same bounds the provider needs to implement its provider trait. Routing a check through `CanUseComponent` therefore forces the compiler to evaluate those bounds and, because they are stated explicitly on the marker, to report the specific one that failed. A missing `name` field surfaces as an unsatisfied `HasField` bound pointing at the context, not as a bare "provider not implemented." `IsProviderFor` is otherwise a near-invisible marker; its whole purpose is to make this error-surfacing work, and check traits are where it earns its keep. - -## Writing checks with the macros - -Rather than spell out check traits by hand, [`check_components!`](../reference/macros/check_components.md) generates them from a short table of components to verify. Given a context and a list of components, it emits a marker trait aliasing `CanUseComponent` and one empty impl per component: - -```rust -check_components! { - Person { - GreeterComponent, - } -} -``` - -The generated impl compiles only if `Person: CanUseComponent`, which in turn drags in the delegated provider's `IsProviderFor` bounds and reports the first that fails. A successful build *is* the passing assertion — these checks have no runtime existence. For components with generic parameters, the parameters to test are listed after a colon, since the check must name them explicitly to have anything concrete to verify. - -For basic wiring, and especially while getting started with CGP, [`delegate_and_check_components!`](../reference/macros/delegate_and_check_components.md) removes that bookkeeping by fusing the two: it wires each entry exactly as [`delegate_components!`](../reference/macros/delegate_components.md) would and derives a check for each delegated key, so a simple context is proven the moment it is written and a newcomer cannot forget the check. Its check trait is named `__CanUse{Context}` while `check_components!` names its trait `__Check{Context}`, deliberately distinct so both can appear once in the same module. Its reach stops at the plain `Component: Provider` form, though — it cannot easily derive checks for advanced mappings such as generic-parameter dispatch (the `open` statement and `@`-path keys) or namespaces — so larger, more advanced codebases keep `delegate_components!` and a standalone [`check_components!`](../reference/macros/check_components.md) separate, which can check those cases and use `#[check_providers]` for per-layer higher-order checks. Plain `delegate_components!` with no check remains right for aggregate providers — the `new`-keyword bundles that dispatch to sub-providers and are delegated to by other contexts, but are not contexts themselves. Using `delegate_and_check_components!` on an aggregate provider is in fact incorrect, not just unnecessary: its `CanUseComponent` check asks whether the target can use each component *as a context*, and an aggregate provider never implements a provider trait with itself in the context position, so the check cannot hold. An aggregate provider is verified instead through a real context that delegates to it, or with a `#[check_providers]` block that asserts `IsProviderFor` on it directly. - -## Checking higher-order providers - -For higher-order providers, the `#[check_providers(...)]` form of [`check_components!`](../reference/macros/check_components.md) checks each provider layer independently rather than checking the context as a whole. Instead of asserting `CanUseComponent` on the context, it asserts [`IsProviderFor`](../reference/traits/is_provider_for.md) directly on each named provider — so `RectangleAreaCalculator` and `ScaledAreaCalculator` are each verified on their own impl. This localizes failures: a dependency missing from the inner `RectangleAreaCalculator` errors on both lines, while one missing only from the outer wrapper errors on the wrapper alone, which narrows down where in a nested stack the gap lives. Provider-level checks are the practical tool for debugging the composition described in [higher-order providers](higher-order-providers.md). - -## Related constructs - -Check traits verify the wiring produced by [`delegate_components!`](../reference/macros/delegate_components.md), and the two macros that write them are [`check_components!`](../reference/macros/check_components.md) for a standalone check and [`delegate_and_check_components!`](../reference/macros/delegate_and_check_components.md) for wiring-and-checking in one step. The assertion they generate is built on [`CanUseComponent`](../reference/traits/can_use_component.md), which combines [`DelegateComponent`](../reference/traits/delegate_component.md) (the lazy table entry whose acceptance is the source of the problem) with [`IsProviderFor`](../reference/traits/is_provider_for.md) (the marker that carries a provider's real bounds so the compiler reports them). The `#[check_providers]` variant of `check_components!` asserts `IsProviderFor` directly on providers, which is the right tool for the [higher-order providers](higher-order-providers.md) it most often checks. - -## Source - -The runtime traits live in [crates/core/cgp-component/src/traits/](../../crates/core/cgp-component/src/traits/) (`CanUseComponent` in `can_use_component.rs`, `IsProviderFor` in `is_provider.rs`, `DelegateComponent` in `delegate_component.rs`); the check-table macros are in [crates/macros/cgp-macro-core/src/types/check_components/](../../crates/macros/cgp-macro-core/src/types/check_components/) and [delegate_and_check_components/](../../crates/macros/cgp-macro-core/src/types/delegate_and_check_components/), with expansion snapshots in [crates/tests/cgp-tests/tests/checking/](../../crates/tests/cgp-tests/tests/checking/). diff --git a/docs/concepts/coherence.md b/docs/concepts/coherence.md deleted file mode 100644 index ba1fa331..00000000 --- a/docs/concepts/coherence.md +++ /dev/null @@ -1,129 +0,0 @@ -# Bypassing coherence - -CGP works around Rust's coherence restrictions by splitting every trait into a pair, so that implementations are written incoherently against a unique provider type and coherence is then restored locally, one context at a time, by an explicit wiring step. - -## What coherence guarantees - -Coherence is the property that every trait lookup resolves to one globally unique implementation, no matter where in the program it is performed. Rust depends on this because its trait system doubles as a dependency-injection mechanism: a generic `impl` can require `where T: Display` without the caller ever naming that bound, and the compiler resolves the dependency — and every transitive dependency beneath it — by global lookup. For that lookup to be sound, it must always find the same implementation, so the answer cannot depend on which crate is asking or what else happens to be in scope. - -Two rules enforce this uniqueness. The **overlap rule** forbids two implementations that could both apply to the same type, since the compiler would have no principled way to choose between them. The **orphan rule** forbids implementing a trait for a type unless the current crate owns either the trait or the type, since otherwise two unrelated crates could each define their own implementation and a program depending on both would face an irreconcilable conflict. Together they are what let dependency injection work transitively without ambiguity. - -## The cost - -These rules are necessary for soundness but they forbid a great deal of code that would be perfectly useful. The canonical casualty is the blanket implementation: one cannot implement `serde::Serialize` for *every* type that implements `Display`, or for every type that implements `AsRef<[u8]>`, because a type might implement several of those bounds at once and the implementations would overlap — and Rust permits at most one such blanket impl, with no way to say which should win. - -Spelled out in code, the second of two overlapping blanket impls is rejected by the compiler, because some type — `String`, say, is both `Display` and `AsRef<[u8]>` — could match both: - -```rust -use serde::{Serialize, Serializer}; - -// Legal on its own: serialize anything printable as a string. -impl Serialize for T { - fn serialize(&self, s: S) -> Result { - s.serialize_str(&self.to_string()) - } -} - -// error[E0119]: conflicting implementation — overlaps the impl above -// on every `T: Display + AsRef<[u8]>`. -impl> Serialize for T { - fn serialize(&self, s: S) -> Result { - s.serialize_bytes(self.as_ref()) - } -} -``` - -The orphan rule bites just as often, and independently of any overlap: a crate that wants to serialize a `Person` type it did not define, using a `Serialize` trait it also did not define, simply cannot, because it owns neither side: - -```rust -use serde::{Serialize, Serializer}; -use other_crate::Person; // defined in a crate we do not own - -// error[E0117]: only traits defined in the current crate can be -// implemented for types defined outside of the crate. -impl Serialize for Person { - fn serialize(&self, s: S) -> Result { - /* ... */ - } -} -``` - -The programmer is left writing the same boilerplate by hand for each type, or pressuring upstream crates to add derives they may not want. - -## Moving `Self` to a parameter - -CGP's first move is to take the type that coherence ranges over — the `Self` of the implementation — and turn it into something the implementing crate always owns. A CGP component is declared once but compiled into two traits: a consumer trait that callers invoke with the ordinary `Self` receiver, and a **provider trait** whose `Self` is a dedicated, zero-sized provider struct while the original receiver becomes an explicit `Context` type parameter. Because the provider implements the provider trait *for its own struct* — `ValueSerializer` for `SerializeBytes`, never `Serialize` for some foreign type — neither coherence rule applies: the `Self` type is local and unique, so the implementation is neither an orphan nor an overlap, however general its `Context` and other parameters are. The full mechanics of this split, and the blanket impls that make a context's `context.serialize(...)` resolve to a chosen provider, are the subject of [consumer and provider traits](consumer-and-provider-traits.md). - -This is what makes incoherent implementations expressible. A crate can define `UseSerde` for any `Value: Serialize`, `SerializeBytes` for any `Value: AsRef<[u8]>`, and `SerializeWithDisplay` for any `Value: Display`, all at once — three blanket implementations of the same capability that overlap freely, since a type like `String` satisfies all three bounds at once, each a distinct provider struct the crate owns. The three impls read almost exactly like the rejected `Serialize` impls above, differing only in the provider name given to [`#[cgp_impl]`](../reference/macros/cgp_impl.md), which becomes the unique `Self` type: - -```rust -#[cgp_impl(UseSerde)] -impl ValueSerializer -where - Value: serde::Serialize, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where S: serde::Serializer { value.serialize(serializer) } -} - -#[cgp_impl(SerializeBytes)] -impl ValueSerializer -where - Value: AsRef<[u8]>, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where S: serde::Serializer { serializer.serialize_bytes(value.as_ref()) } -} - -#[cgp_impl(new SerializeWithDisplay)] -#[uses(CanSerializeValue)] -impl ValueSerializer -where - Value: core::fmt::Display, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where S: serde::Serializer { self.serialize(&value.to_string(), serializer) } -} -``` - -Because each impl targets its own `Self` — `UseSerde`, `SerializeBytes`, `SerializeWithDisplay` — all three compile together and overlap on `String` without complaint, where any two of the vanilla `Serialize` impls were rejected. A downstream crate can add yet more for types it does not own, since the orphan rule never enters: the `Self` type is always a struct that crate defines. - -## Restoring coherence locally - -Removing coherence from implementations would be useless if it also removed coherence from *use* — a caller still needs `context.serialize(value, s)` to mean one definite thing. CGP's second move is therefore to reintroduce coherence at a smaller scale: instead of one global choice of implementation per trait, each context makes its own coherent choice, valid within that context alone. The insight is that incoherence is wanted only when writing implementations; when using them, what is wanted is many independent local scopes, each internally consistent. - -The wiring step is what creates those scopes. A concrete context selects exactly one provider per component by becoming a type-level lookup table — written with [`delegate_components!`](../reference/macros/delegate_components.md) — whose entry for a component names the provider to use. Within that one context the choice is unambiguous, so `context.serialize(...)` resolves coherently; a different context can wire the same component to a different provider and resolve differently, with no conflict between them because the resolution is keyed on the context type. - -Two applications can thus serialize `Vec` as hexadecimal and as base64 respectively, each coherent in its own scope, where global coherence would have forced a single answer on both. The two scopes are simply two wiring tables, each opening the serialization component and choosing a provider for `Vec`: - -```rust -delegate_components! { - AppA { - open ValueSerializerComponent; - @ValueSerializerComponent.Vec: SerializeHex, - } -} - -delegate_components! { - AppB { - open ValueSerializerComponent; - @ValueSerializerComponent.Vec: SerializeBase64, - } -} -``` - -`AppA` serializes a `Vec` as hexadecimal and `AppB` as base64; the overlapping `SerializeHex` and `SerializeBase64` providers coexist globally, while each context's choice stays unambiguous within itself. The `open` statement and its `@`-path entries are the [namespace](namespaces.md)-based wiring form that keys this dispatch on the value type. Because the table is consulted only at the leaves of dependency injection and never appears in a generic bound, the soundness problem that motivated the overlap rule — a blanket impl silently chosen inside generic code and later contradicted by a specialized one — cannot arise: there is no global blanket impl to be silently chosen, only a per-context entry. - -## Composing and selecting incoherent implementations - -Two further patterns let a context draw on many incoherent implementations at once rather than picking a single one. When the choice of provider should depend on a type argument — serialize *this* value type one way and *that* one another — the [`open` statement](../reference/macros/delegate_components.md) wires a provider per value of that argument directly into the context's table, so one component fans out to a type-specific provider per case; this is the [dispatching](dispatching.md) idea applied to a generic parameter, resolved through the [`RedirectLookup`](../reference/providers/redirect_lookup.md) impl each component carries. (The legacy [`UseDelegate`](../reference/providers/use_delegate.md) provider performs the same second lookup through a separate nested table.) When one implementation should be expressed in terms of another — wrap, scale, or recurse over a base case — a [higher-order provider](higher-order-providers.md) takes the inner provider as a parameter and composes with it. Both compose entirely in types, so a context can wire a deep tree of overlapping providers and pay nothing at runtime. - -Dependency injection is what threads the chosen implementations through without explicit plumbing. A provider states the capabilities it needs as bounds in its `where` clause — that the context can serialize a `String`, that it can fetch an arena allocator — and the wiring satisfies them by resolving each through the same context, exactly the [impl-side dependency](impl-side-dependencies.md) mechanism ordinary CGP code uses. The context therefore acts as the implicit carrier of every implementation a computation needs, which is how CGP emulates capability-passing — supplying values and behaviors to deeply nested code through the context rather than as explicit arguments — in stable Rust. - -## Related constructs - -The trait split that makes incoherent implementations expressible is documented in full in [consumer and provider traits](consumer-and-provider-traits.md), generated by [`#[cgp_component]`](../reference/macros/cgp_component.md) and implemented through [`#[cgp_impl]`](../reference/macros/cgp_impl.md). The local-coherence wiring step is [`delegate_components!`](../reference/macros/delegate_components.md), which builds the per-context [`DelegateComponent`](../reference/traits/delegate_component.md) table; [`check_components!`](../reference/macros/check_components.md) verifies that a context's chosen providers actually satisfy their dependencies, the subject of [check traits](check-traits.md). The two composition patterns are per-value dispatch through the [`open` statement](../reference/macros/delegate_components.md) and its [`RedirectLookup`](../reference/providers/redirect_lookup.md) resolution (see [namespaces](namespaces.md) and [dispatching](dispatching.md); the legacy [`UseDelegate`](../reference/providers/use_delegate.md) is the older equivalent) and [higher-order providers](higher-order-providers.md), and the dependency-threading that ties them to a context is [impl-side dependencies](impl-side-dependencies.md). The [modularity hierarchy](modularity-hierarchy.md) concept places this strategy on a ladder, showing how the trait split and per-context wiring described here are the upper rungs above vanilla Rust's one-implementation-per-type coherence. The [modular serialization](../examples/modular-serialization.md) example works the whole strategy through end to end on Serde's `Serialize` and `Deserialize`. - -## Source - -The blanket impls that restore coherent use from the provider traits, and the [`DelegateComponent`](../reference/traits/delegate_component.md)/[`IsProviderFor`](../reference/traits/is_provider_for.md)/[`CanUseComponent`](../reference/traits/can_use_component.md) machinery they rely on, live in [crates/core/cgp-component/src/](../../crates/core/cgp-component/src/); the macros that generate them are in [crates/macros/cgp-macro-core/src/types/cgp_component/](../../crates/macros/cgp-macro-core/src/types/cgp_component/). diff --git a/docs/concepts/consumer-and-provider-traits.md b/docs/concepts/consumer-and-provider-traits.md deleted file mode 100644 index 2916b9ff..00000000 --- a/docs/concepts/consumer-and-provider-traits.md +++ /dev/null @@ -1,73 +0,0 @@ -# Consumer and provider traits - -The defining idea of CGP is that one trait definition becomes two traits — a consumer trait that callers use and a provider trait that implementers write — so that many independent implementations of the same capability can coexist without violating Rust's coherence rules. - -## The problem - -Rust conflates using a capability with implementing it, and its coherence rules then permit only one implementation per type. A plain `trait CanCalculateArea` is implemented by the same type that callers invoke `.area()` on, so a crate can supply at most one `area` behavior for a given context, and an implementation written in a downstream crate runs into the orphan rule. This is exactly the wall a programmer hits when they want two interchangeable strategies for the same operation, or want to define a behavior for a type they do not own. - -CGP's answer is to split the single trait into a pair. The capability is still declared once, but the declaration is compiled into two related traits whose roles are kept apart: one is what code *calls*, the other is what code *implements*. Pulling these two roles into separate traits is what lets an unlimited number of implementations live side by side, because the type that carries each implementation is no longer the context. - -## The duality - -A consumer trait is the caller's view: it keeps the original `Self` receiver, so a caller writes `context.area()` exactly as with an ordinary trait. A provider trait is the implementer's view: the original `Self` is moved out into an explicit leading `Context` type parameter, and every `self`/`Self` becomes `context`/`Context`. The provider trait is then implemented not for the context but for a dedicated, zero-sized **provider** struct — a type-level-only name like `RectangleArea` that is never instantiated and carries no runtime value. - -Moving `Self` to a parameter is what sidesteps coherence. Because a provider implements `AreaCalculator` for *its own* struct over a generic `Context`, rather than implementing a trait for the context, the orphan and overlap rules do not bite: a crate can define `RectangleArea`, `CircleArea`, and any number of further providers for the same component, each a distinct `Self` type, all valid at once. The cost is that the provider-trait shape reads inside-out, which is why providers are normally written through the sugar described below rather than by hand. - -These two traits are produced from one definition by [`#[cgp_component]`](../reference/macros/cgp_component.md), the macro that turns an ordinary trait into a full CGP **component** — the consumer trait, the provider trait, a zero-sized component-name struct used as a key, and the blanket impls that connect them. - -## How the two traits connect - -Two generated blanket impls bridge the consumer and provider sides, and together they make `context.area()` resolve to a chosen provider without the caller naming it. The **consumer blanket impl** says that any context which implements the provider trait *for itself* automatically gets the consumer trait, forwarding `context.area()` to `Context::area(self)`. The **provider blanket impl** says that any provider which delegates the component inherits the provider trait from whatever it delegates to, looked up through [`DelegateComponent`](../reference/traits/delegate_component.md). - -Wiring is what supplies that delegation. A concrete context picks one provider per component by becoming a type-level table whose entry for `AreaCalculatorComponent` names the chosen provider; [`delegate_components!`](../reference/macros/delegate_components.md) writes that table. The two blanket impls then chain through it: `context.area()` resolves through the consumer impl to the context implementing the provider trait for itself, which resolves through the provider impl to the table entry, landing on the selected provider's `area`. Selecting a different provider in the table is the only change needed to swap the behavior — no caller is touched. - -The marker [`IsProviderFor`](../reference/traits/is_provider_for.md) rides along on the provider trait as a supertrait, capturing each provider's dependencies so that a missing one surfaces as a readable compiler error rather than a bare "trait not implemented". It is wiring's bookkeeping, not something a provider author writes; it is generated for them. - -## In practice - -Most code never sees the raw provider-trait shape, because the sugar restores the familiar look of an ordinary trait impl. A provider is written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), which lets the author keep `self`, `Self`, and the consumer-trait signatures while the macro mechanically rewrites the block into the provider-trait form and emits the `IsProviderFor` impl. The lower-level [`#[cgp_provider]`](../reference/macros/cgp_provider.md) is the same machinery without the `self`/`Self` rewrite, for when the inside-out shape is wanted explicitly. - -A complete component ties the pieces together: define the trait, write a provider, wire it, call it. - -```rust -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} - -#[derive(HasField)] -pub struct Rectangle { - pub width: f64, - pub height: f64, -} - -delegate_components! { - Rectangle { - AreaCalculatorComponent: RectangleArea, - } -} - -fn print_area(rect: &Rectangle) { - println!("area = {}", rect.area()); // CanCalculateArea, via RectangleArea -} -``` - -A context can also implement a consumer trait directly, exactly as it would a vanilla Rust trait, when code reuse is not the goal. The consumer/provider split is a superset of ordinary traits, not a replacement: the provider machinery is what you opt into when a capability needs more than one implementation, and skipping it costs nothing for the simple case. - -A second provider mirrors the consumer relationship in reverse. [`UseContext`](../reference/providers/use_context.md) is a built-in provider that implements the provider trait *by routing back through the context's own consumer-trait implementation* — the dual of the consumer blanket impl, and the hook that lets a higher-order provider fall back to whatever the context already has wired. - -## Related constructs - -[`#[cgp_component]`](../reference/macros/cgp_component.md) is the macro that generates the whole component — both traits, the component-name struct, and the blanket impls — and is the doc to read for the exact expansion. Providers are written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) (consumer-trait-style sugar) or its lower layer [`#[cgp_provider]`](../reference/macros/cgp_provider.md); [`#[cgp_fn]`](../reference/macros/cgp_fn.md) is the lighter alternative when only a single implementation is ever needed and no wiring is wanted. - -The connective tissue is documented in the traits and provider sections. [`DelegateComponent`](../reference/traits/delegate_component.md) is the type-level table the provider blanket impl reads, written by [`delegate_components!`](../reference/macros/delegate_components.md); [`IsProviderFor`](../reference/traits/is_provider_for.md) is the dependency-tracking marker on the provider trait; and [`UseContext`](../reference/providers/use_context.md) is the provider that closes the loop back to the consumer side. For the dependency-injection idea that providers express in their `where` clauses, see [impl-side dependencies](impl-side-dependencies.md); for why the trait split is needed in the first place and how local wiring restores coherent use, see [bypassing coherence](coherence.md). diff --git a/docs/concepts/dispatching.md b/docs/concepts/dispatching.md deleted file mode 100644 index 109e2ec7..00000000 --- a/docs/concepts/dispatching.md +++ /dev/null @@ -1,39 +0,0 @@ -# Dispatching - -Dispatching is the CGP pattern for handling an extensible-data value generically by routing it to per-field or per-variant handlers: given an enum, match its current variant to the handler for that variant; given a record, route each field to the handler that produces it. - -## Purpose - -Dispatching solves the problem of writing a single piece of logic that works over a record or enum whose exact shape is not known where the logic is written. A concrete `match` on an enum names every variant in one place and calls a fixed function for each, and a struct literal supplies every field at once — both bake the data's shape into the code. Dispatching keeps the same per-variant and per-field structure but lets the shape and the handlers be chosen by type, so the same matcher can serve many enums and the same builder can serve many records, with each variant's or field's behavior wired separately like any other CGP component. - -The pattern exists because the extensible-data machinery already represents records and enums as type-level lists, and dispatching is what turns those representations into useful work. The extractor family in [`extract_field`](../reference/traits/extract_field.md) can take an enum apart one variant at a time, and the builder family in [`has_builder`](../reference/traits/has_builder.md) can assemble a record one field at a time, but neither decides *what to do* with each variant or field. Dispatching supplies that decision: it pairs each variant with a handler and runs them as a matcher, or pairs each field with a handler and runs them as a builder, expressing both as ordinary [`Computer`](../reference/components/computer.md)/[`Handler`](../reference/components/handler.md) providers so they compose with the rest of CGP. The concrete providers that implement the pattern are documented in [`dispatch_combinators`](../reference/providers/dispatch_combinators.md); this page explains the idea they share. - -## Matching a variant to a handler - -Matching an enum is a loop that tries each variant in turn and stops at the first one that is actually present. The matcher starts by converting the input value into its *extractor* — the partial enum from [`HasExtractor`](../reference/traits/extract_field.md) in which every variant is still possible — and then runs a list of per-variant handlers over it. Each handler attempts one variant: if the runtime value is that variant, the handler pulls out the payload, runs the variant's own handler on it, and reports success; if not, it reports failure and hands back a *remainder*, the extractor with that one variant ruled out at the type level. The next handler in the list receives that shrunken remainder, so each miss narrows the set of variants still in play. - -The narrowing is what makes the match provably exhaustive without a wildcard arm. Because each failed attempt rules out one more variant in the type, the remainder after the last handler has every variant ruled out and is therefore an uninhabited type — a value that cannot exist. The matcher discharges it with `finalize_extract`, which is sound precisely because there is no value to handle, and the compiler accepts the discharge with no fallback case. Adding a variant to the enum without adding a handler for it makes the final remainder inhabited again, so the code fails to compile until the new variant is handled. This is the same exhaustiveness guarantee a concrete `match` gives, recovered for a generic matcher. - -The loop itself is a monadic pipeline. Each handler returns `Result` — `Ok` for a match, `Err` for a miss carrying the remainder — and the matcher runs the list under a monad that short-circuits on `Ok` and threads the `Err` forward, so the list executes handler by handler until one matches. The per-variant handler list is assembled from small adapters: an extract adapter that tries one variant and forwards its payload, and an unwrap adapter that strips the variant name off the payload before handing the bare value to the variant's computer. A matcher can also build this list automatically from the enum's own variant list, so that wiring an enum to a matcher requires naming the matcher alone rather than spelling out a handler per variant. - -## Routing fields of a record to handlers - -Building a record is the mirror image of matching a variant: instead of taking a value apart and stopping at one branch, it starts from nothing and runs a handler for every field. The builder begins with an empty *partial record* from [`HasBuilder`](../reference/traits/has_builder.md), in which every field is marked absent, and pipes that partial record through a list of per-field handlers. Each handler computes one field's value — it may read the fields already set on the partial record, since it sees the builder by reference — and sets that field, advancing the partial record so one more field is now present. After the whole list has run, the partial record has every field present, and the builder finalizes it into the concrete struct. - -Finalizing is where the per-field tracking becomes load-bearing. The operation that turns a partial record back into the concrete struct is available only when every field is present, so a builder that omits a handler for some field cannot finalize and fails to compile, rather than producing a half-built value at runtime. Just as a matcher proves it covered every variant, a builder proves it filled every field. The field handlers are again small adapters: one that computes and sets a single named field, and one that computes a whole record's worth of fields and merges all the shared ones in at once, which is convenient for extending an existing record with a few extra fields. - -The matcher and builder directions are two uses of the same underlying idea — a type-level list of per-element handlers run as a CGP provider — applied to the two halves of the extensible-data system. Matching consumes a sum type and the list runs until one element succeeds; building produces a product type and the list runs over every element. Both are expressed as handler providers, so a dispatcher can appear anywhere a `Computer` or `Handler` is expected: wired into a context, nested inside another dispatcher to handle a group of variants with a sub-matcher, or chained with other handler combinators. - -## How the pieces fit together - -Dispatching is the top layer of a stack, and each layer below it supplies one capability the layer above relies on. At the base, the extensible-data derives give an enum the extractor traits and a record the builder traits, and expose the type-level variant or field list through [`has_fields`](../reference/traits/has_fields.md). The [`extract_field`](../reference/traits/extract_field.md) family turns those into the per-variant extraction-and-remainder operations, and the [`has_builder`](../reference/traits/has_builder.md) family into the per-field set-and-finalize operations. On top of those, the [handler components](../reference/components/handler.md) — [`Computer`](../reference/components/computer.md), `TryComputer`, and `Handler` — provide the uniform "context, code, input → output" interface that every per-element handler and every dispatcher speaks. The matcher loop is then a monadic pipeline ([`PipeMonadic`](../reference/providers/monad_providers.md) under an `Ok`-threading monad) and the builder a handler pipeline, both producing ordinary providers. - -The concrete combinators that occupy the top layer are documented in [`dispatch_combinators`](../reference/providers/dispatch_combinators.md). On the matching side they are the `MatchWithHandlers` family (owned, by-reference, and by-mutable-reference), the `MatchFirstWithHandlers` family for handlers that take extra arguments, and the `MatchWithValueHandlers`/`MatchWithFieldHandlers` conveniences that build the handler list from the enum's variants; the per-variant adapters are `ExtractFieldAndHandle`, `HandleFieldValue`, and the grouped-variant `DowncastAndHandle`. On the building side they are `BuildWithHandlers`, which drives the whole assembly, and the per-field adapters `BuildAndSetField` and `BuildAndMerge`. When the goal is simply to turn a Rust trait with one impl per type into a variant-dispatching impl over an enum, the attribute macro [`#[cgp_auto_dispatch]`](../reference/macros/cgp_auto_dispatch.md) generates the matcher wiring automatically, so the pattern can be used without naming any of these providers directly. - -## Related constructs - -The providers that implement this pattern are catalogued in [`dispatch_combinators`](../reference/providers/dispatch_combinators.md). The matching half rests on [`extract_field`](../reference/traits/extract_field.md) and the building half on [`has_builder`](../reference/traits/has_builder.md), with the variant or field list coming from [`has_fields`](../reference/traits/has_fields.md). Every dispatcher and per-element handler is a member of the handler families documented in [`handler`](../reference/components/handler.md) and [`computer`](../reference/components/computer.md), and the matcher loop is the monadic pipeline from [`monad_providers`](../reference/providers/monad_providers.md). The macro that automates the whole pattern for a per-type trait is [`#[cgp_auto_dispatch]`](../reference/macros/cgp_auto_dispatch.md), and the broader idea of running providers as composable handlers is covered in [`handlers`](handlers.md). The data-type view behind the two directions — what a record and an enum are as extensible structures — is in [extensible records](extensible-records.md) and [extensible variants](extensible-variants.md), worked through end to end in the [application builder](../examples/application-builder.md) example for records and the [extensible shapes](../examples/extensible-shapes.md) and [expression interpreter](../examples/expression-interpreter.md) examples for variants. - -## Source - -The dispatching pattern is implemented entirely by the providers in [crates/extra/cgp-dispatch/src/providers/](../../crates/extra/cgp-dispatch/src/providers/), which build on the extractor and builder traits in [crates/core/cgp-field/src/traits/](../../crates/core/cgp-field/src/traits/) and the handler components in [crates/extra/cgp-handler/src/](../../crates/extra/cgp-handler/src/). The matcher loop reuses the monadic pipeline from [crates/extra/cgp-monad/src/](../../crates/extra/cgp-monad/src/). Tests covering both directions are in [crates/tests/cgp-tests/tests/extensible_records/](../../crates/tests/cgp-tests/tests/extensible_records/) and [crates/tests/cgp-tests/tests/extensible_variants/](../../crates/tests/cgp-tests/tests/extensible_variants/), and the macro that generates dispatch wiring is exercised in [crates/tests/cgp-tests/tests/dispatching/](../../crates/tests/cgp-tests/tests/dispatching/). diff --git a/docs/concepts/extensible-records.md b/docs/concepts/extensible-records.md deleted file mode 100644 index 520cf47a..00000000 --- a/docs/concepts/extensible-records.md +++ /dev/null @@ -1,75 +0,0 @@ -# Extensible records - -Extensible records let code build and read a struct through the type-level names and types of its fields, without naming the concrete struct, so that independent components can each contribute part of a record and have their contributions merged into a whole. - -## Purpose - -Extensible records solve the problem that a plain Rust struct is closed against incremental, decoupled construction. A struct literal requires every field to be supplied at one place that names the concrete type, and a hand-written constructor that grows with each new subsystem becomes a single point that every change must edit. Extensible records break that coupling by treating a struct as a *product of named fields*: code can ask for "any context with a `db_path` field" or assemble "whatever struct these field contributions add up to" without ever spelling out the full type. This brings the row-polymorphism of languages like PureScript and the structural typing of records to Rust, expressed entirely through traits and resolved at compile time. - -The payoff is the **extensible builder pattern**, where the construction of one struct is split across several independent providers — one per subsystem — that need not know the final type or each other. Each provider builds a small piece, and a dispatcher merges the pieces into the target struct. Because the pieces are decoupled, the same provider can feed many different target structs, and a target struct can be assembled from a different mix of providers per context, with the compiler verifying that every required field is supplied. The [application builder](../examples/application-builder.md) example develops this pattern end to end. - -## A record as a product of named fields - -The foundation is the view of a struct as a list of named fields, exposed by [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md). The output struct that a builder produces in the [application builder](../examples/application-builder.md) example derives the three record traits together: - -```rust -#[derive(HasField, HasFields, BuildField)] -pub struct SqliteClient { - pub sqlite_pool: SqlitePool, -} -``` - -Deriving `HasFields` gives the struct a `Fields` associated type that is a [`Product!`](../reference/macros/product.md) of [`Field`](../reference/types/field.md) entries — the type-level spelling of its shape, one entry per field tagged by name: - -```rust -type Fields = Product![Field]; -``` - -Reading a single field by name is the older and simpler capability provided by [`#[derive(HasField)]`](../reference/derives/derive_has_field.md), which generates one [`HasField`](../reference/traits/has_field.md) impl per field keyed on a [`Symbol!`](../reference/macros/symbol.md) tag, and is what getter traits written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) resolve against. Reading is only half the story; constructing a struct field by field is what extensible records add on top. - -## Partial records and the builder traits - -Incremental construction runs through a *partial record* — a companion type generated by [`#[derive(BuildField)]`](../reference/derives/derive_build_field.md) that carries one [`MapType`](../reference/traits/map_type.md) marker per field recording whether that field is present yet. The marker `IsPresent` stores the field's real value and `IsNothing` stores `()`, so a partial record with every marker `IsNothing` is an empty builder and one with every marker `IsPresent` is a fully populated value. The [builder trait family](../reference/traits/has_builder.md) walks between these states: `HasBuilder::builder()` produces the empty partial record, each `build_field` call flips one marker from `IsNothing` to `IsPresent`, and `FinalizeBuild::finalize_build` turns the partial record back into the concrete struct. - -The tracking is what makes the pattern safe. Because `finalize_build` is implemented only for the all-present configuration of the partial record, finalizing a value with any field still absent is a compile error rather than a runtime panic, and the order in which fields are built does not matter. In the [application builder](../examples/application-builder.md) example the lifecycle assembles the application context — `builder()` produces the empty partial `App`, each step fills more fields, and `finalize_build` closes it out: - -```rust -let app: App = App::builder() // partial App: every field IsNothing - .build_from(sqlite_client) // the SqliteClient's field is now IsPresent - .build_from(http_client) // ... - .build_from(open_ai_client) // every remaining field now IsPresent - .finalize_build(); // FinalizeBuild exists only at the all-present configuration -``` - -Moving `finalize_build` earlier, before every field is set, would not type-check. The reverse direction is equally useful: `IntoBuilder` turns a complete struct into an all-present partial record, and `TakeField` removes one field at a time, which is how one record's fields are moved into another's builder. - -## Merging one record into another - -Moving the shared fields of a source struct into a target builder in a single step is the job of `CanBuildFrom`, documented with the other [structural casts](../reference/traits/cast.md). Its `build_from` method recurses over the source's field list, using `TakeField` to pull each field out of the source and `BuildField` to write it into the target, so a builder can absorb fields from several smaller structs in sequence before being finalized. This is what lets a small struct like a database client be merged into a larger application struct without either type knowing about the other — they share only the field names, and the names are matched at the type level. - -## The extensible builder pattern - -The builder dispatcher turns a list of independent builder providers into a single provider that constructs a whole struct. Each subsystem is built by a [`Handler`](../reference/components/handler.md) (or [`Computer`](../reference/components/computer.md)) provider that produces a small output struct, and `BuildAndMergeOutputs` — one of the [dispatch combinators](../reference/providers/dispatch_combinators.md) — runs every provider, merges each output into the target's builder via `build_from`, and finalizes the result. Wiring it for a context is a single entry naming the target struct and the provider list: - -```rust -delegate_components! { - FullAppBuilder { - HandlerComponent: - BuildAndMergeOutputs, - } -} -``` - -Because the dispatcher is generic over the target and the provider list, swapping a subsystem (a different database, a different model) means changing one entry in the list, and selecting among several target structs is a matter of [code-based dispatch](../reference/providers/use_delegate.md) on the builder's `Code` parameter. The general routing mechanism this rests on is described in [dispatching](dispatching.md). - -## Related constructs - -Extensible records are the product half of CGP's extensible data; the sum half is covered in [extensible variants](extensible-variants.md), and the two are deliberately dual. The shape of a record comes from [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md) (or the all-in-one [`#[derive(CgpData)]`](../reference/derives/derive_cgp_data.md)), individual field reads from [`HasField`](../reference/traits/has_field.md), and incremental construction from the [builder family](../reference/traits/has_builder.md) generated by [`#[derive(BuildField)]`](../reference/derives/derive_build_field.md). The presence markers are [`MapType`](../reference/traits/map_type.md) implementations, merging is `CanBuildFrom` from [casting](../reference/traits/cast.md), and the builder dispatcher is among the [dispatch combinators](../reference/providers/dispatch_combinators.md), built on the [handler families](handlers.md). The worked example is the [application builder](../examples/application-builder.md). - -## Source - -The record traits live in [crates/core/cgp-field/src/traits/](../../crates/core/cgp-field/src/traits/) (`has_builder.rs`, `build_field.rs`, `update_field.rs`, `take_field.rs`) with the merge logic in [crates/core/cgp-field/src/impls/build_from.rs](../../crates/core/cgp-field/src/impls/build_from.rs) and the `MapType` markers in [crates/core/cgp-field/src/impls/map_type.rs](../../crates/core/cgp-field/src/impls/map_type.rs). The builder dispatchers are in [crates/extra/cgp-dispatch/src/providers/](../../crates/extra/cgp-dispatch/src/providers/). End-to-end tests are under [crates/tests/cgp-tests/tests/extensible_records/](../../crates/tests/cgp-tests/tests/extensible_records/). diff --git a/docs/concepts/extensible-variants.md b/docs/concepts/extensible-variants.md deleted file mode 100644 index 907af907..00000000 --- a/docs/concepts/extensible-variants.md +++ /dev/null @@ -1,88 +0,0 @@ -# Extensible variants - -Extensible variants let code construct and deconstruct an enum through the type-level names and types of its variants, without naming the concrete enum, so that independent components can each handle one variant and operate on any enum that contains it. - -## Purpose - -Extensible variants are the dual of [extensible records](extensible-records.md): where a record is a product of named fields, an enum is a *sum of named variants*, and the same name-driven, decoupled treatment applies. They address the [expression problem](https://en.wikipedia.org/wiki/Expression_problem) — the difficulty of adding new variants to a data type and new operations over it without editing existing code. A concrete `enum` plus a `match` bakes the full set of variants into every function that consumes it, so adding a variant forces every `match` to change, and a variant defined in an upstream crate cannot be handled without forking. Extensible variants decouple each variant's handling from the enum definition, bringing polymorphic variants in the style of OCaml and structural sum types to Rust through traits resolved at compile time. - -The payoff is the **extensible visitor pattern**, where the logic for each variant lives in its own provider and a dispatcher routes a value to the provider for its current variant. New variants can be added without touching the handlers for the others, new operations can be added without touching the enum, and the same handler set can serve several enums that share variants. The [expression interpreter](../examples/expression-interpreter.md) example develops this pattern for a recursive arithmetic language, solving the expression problem, and the [extensible shapes](../examples/extensible-shapes.md) example applies it to a non-recursive enum of geometric shapes. - -## An enum as a sum of named variants - -The shape of an enum is exposed by [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md), whose `Fields` associated type for an enum is a [`Sum!`](../reference/macros/sum.md) of [`Field`](../reference/types/field.md) entries built on the [`Either`/`Void`](../reference/types/either.md) spine, mirroring the `Product!`/`Cons` spine that a record uses. The expression enum in the [expression interpreter](../examples/expression-interpreter.md) example derives the three variant traits together: - -```rust -#[derive(HasFields, FromVariant, ExtractField)] -pub enum MathExpr { - Plus(Plus), - Times(Times), - Literal(Literal), -} -``` - -Its `HasFields` shape is a sum of one `Field` per variant, each tagged by the variant name: - -```rust -type Fields = Sum![ - Field>, - Field>, - Field>, -]; -``` - -Constructing an enum from one of its variants generically is [`FromVariant`](../reference/traits/from_variant.md), generated by [`#[derive(FromVariant)]`](../reference/derives/derive_from_variant.md), and deconstructing it one variant at a time is the extractor family generated by [`#[derive(ExtractField)]`](../reference/derives/derive_extract_field.md). One restriction shapes all of this: a derivable enum must follow the sum-of-products form, where each variant holds exactly one unnamed field — wrap richer payloads in a dedicated struct so the variant's `Value` type stays a single, nameable type. The standalone `Plus`, `Times`, and `Literal` types each variant wraps are exactly such payload structs. - -## Partial variants and the extractor traits - -Deconstruction runs through a *partial variant* — the enum companion that the extractor derive generates, carrying one [`MapType`](../reference/traits/map_type.md) marker per variant, just as a partial record carries one per field. The crucial difference is the marker used for absence: a record uses `IsNothing` (storing `()`), while a variant uses `IsVoid`, which maps every variant to the uninhabited [`Void`](../reference/types/either.md) type. The [extractor family](../reference/traits/extract_field.md) drives the deconstruction: `HasExtractor::to_extractor` converts the enum into a partial variant with every variant still `IsPresent`, and `ExtractField::extract_field` tries to pull out one variant, returning `Result` — `Ok` with the payload if the value is that variant, `Err` with the *remainder* (the partial variant with that variant marked `IsVoid`) otherwise. Deconstructing an enum by hand reads as a chain of `extract_field` calls, each handling one variant and passing the remainder to the next: - -```rust -match expr.to_extractor() // partial MathExpr: every variant still possible - .extract_field(PhantomData::) -{ - Ok(Literal(value)) => value, // it was the Literal variant - Err(remainder) => { - // remainder's type now has Literal ruled out (IsVoid); try the next variant - ... - } -} -``` - -Marking extracted variants as `Void` is what gives compile-time exhaustiveness without a wildcard arm. Each failed `extract_field` rules out one more variant in the type, so after every variant has been tried the remainder has every marker `IsVoid` and is therefore uninhabited — a value that cannot exist. `FinalizeExtract::finalize_extract` discharges such a remainder with an empty `match`, which is sound precisely because no value can reach it, and the compiler accepts the absence of a fallback case. Add a variant to the enum without handling it and the final remainder becomes inhabited again, so the code fails to compile until the new variant is covered — the same guarantee a concrete `match` gives, recovered for generic code. - -## Upcasting and downcasting - -Because variants are matched by name, two enums that share a subset of variants can interconvert with no hand-written conversion, through the [casting traits](../reference/traits/cast.md). `CanUpcast` lifts a value of a narrow enum into a wider one whose variants are a superset — it always succeeds, since every source variant has a home in the target. `CanDowncast` goes the other way, narrowing a wide enum into a smaller one and succeeding only if the value's current variant exists in the target, otherwise handing back a remainder that can be downcast again against another candidate. Both reuse the same extractor recursion that powers deconstruction, differing only in whether the variant list comes from the source or the target. Upcasting is also how a provider can construct a value using only the subset of variants it cares about: it builds a small local enum and upcasts it into the full type, the variant-side analog of reading a field through a getter. In the [expression interpreter](../examples/expression-interpreter.md) example a to-Lisp provider does exactly this, constructing a two-variant local enum and lifting each value into the complete `LispExpr`: - -```rust -let ident = LispSubExpr::Ident(Ident("+".to_owned())).upcast(PhantomData::); -``` - -## The extensible visitor pattern - -The visitor dispatcher turns a per-variant handler set into a single provider over the whole enum. `MatchWithValueHandlers` and its by-reference counterpart `MatchWithValueHandlersRef` — among the [dispatch combinators](../reference/providers/dispatch_combinators.md) — take the enum's variant list, derive one extract-and-handle step per variant, and run them as a monadic pipeline that short-circuits on the first matching variant and threads the remainder forward otherwise. Each variant is handled by a [`Computer`](../reference/components/computer.md) or [`ComputerRef`](../reference/components/computer.md) provider, so the operation composes with the rest of CGP, and the dispatcher's optional provider parameter defaults to [`UseContext`](../reference/providers/use_context.md) so a context can override the handling of individual variants in its wiring. Wiring an enum to a matcher names one provider per variant in a table and routes the whole enum through the dispatcher: - -```rust -delegate_components! { - Interpreter { - ComputerComponent: - UseInputDelegate: EvalAdd, // one provider per variant - Times: EvalMultiply, - Literal: EvalLiteral, - }>, - } -} -``` - -The `MathExpr` entry routes to `DispatchEval`, a thin context-specific provider that defers to `MatchWithValueHandlers`; that wrapper exists to break the trait-resolution cycle between the matcher and the per-variant providers it dispatches to. The routing mechanism is described in full in [dispatching](dispatching.md), and the monadic pipeline that sequences the per-variant steps in [monadic handlers](monadic-handlers.md). - -## Related constructs - -Extensible variants are the sum half of CGP's extensible data; the product half is [extensible records](extensible-records.md), and the two share their `MapType` markers, their casting traits, and their dispatch machinery. An enum's shape comes from [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md) (or the all-in-one [`#[derive(CgpData)]`](../reference/derives/derive_cgp_data.md)), construction from [`FromVariant`](../reference/traits/from_variant.md) via [`#[derive(FromVariant)]`](../reference/derives/derive_from_variant.md), and deconstruction from the [extractor family](../reference/traits/extract_field.md) via [`#[derive(ExtractField)]`](../reference/derives/derive_extract_field.md). The presence markers are [`MapType`](../reference/traits/map_type.md) implementations, conversions are `CanUpcast`/`CanDowncast` from [casting](../reference/traits/cast.md), and the visitor dispatchers are among the [dispatch combinators](../reference/providers/dispatch_combinators.md), built on the [handler families](handlers.md) and [monadic handlers](monadic-handlers.md). The worked examples are the [expression interpreter](../examples/expression-interpreter.md), which applies the pattern to a recursive language, and [extensible shapes](../examples/extensible-shapes.md), which applies it to a non-recursive enum. - -## Source - -The variant traits live in [crates/core/cgp-field/src/traits/](../../crates/core/cgp-field/src/traits/) (`extract_field.rs`, `from_variant.rs`) with `HasExtractor`, `FinalizeExtract`, and the casting recursion in [crates/core/cgp-field/src/impls/cast.rs](../../crates/core/cgp-field/src/impls/cast.rs), and the `IsVoid` marker in [crates/core/cgp-field/src/impls/map_type.rs](../../crates/core/cgp-field/src/impls/map_type.rs). The visitor dispatchers and their monadic pipeline are in [crates/extra/cgp-dispatch/src/providers/](../../crates/extra/cgp-dispatch/src/providers/) and [crates/extra/cgp-monad/src/](../../crates/extra/cgp-monad/src/). End-to-end tests are under [crates/tests/cgp-tests/tests/extensible_variants/](../../crates/tests/cgp-tests/tests/extensible_variants/). diff --git a/docs/concepts/handlers.md b/docs/concepts/handlers.md deleted file mode 100644 index 0bbae495..00000000 --- a/docs/concepts/handlers.md +++ /dev/null @@ -1,43 +0,0 @@ -# Handlers - -The handler family is a group of CGP components that all model the same shape — turning an `Input` value into an `Output` value under a phantom `Code` tag — while varying along the axes of synchrony, fallibility, and how the input is passed. - -## The idea - -A handler is a provider that transforms an `Input` into an `Output`, with the kind of computation it performs selected by a phantom `Code` tag. Every component in this family follows the same `Code`/`Input`/`Output` model: the consumer method takes a `PhantomData` to say *which* computation is wanted, takes the `Input` value to compute over, and produces an associated `Output` type. The `Code` tag carries no data — it exists purely so that one context can host many different handlers, each keyed by a distinct tag type, and so that wiring can dispatch on it. This is why the methods thread a `PhantomData` argument rather than a real value. - -The family exists because real computations differ along a few independent dimensions, and CGP gives each combination its own component so that a provider declares exactly the capabilities it has. A pure function that adds two numbers needs nothing from the context; a function that reads a file may fail and must work with the context's error type; a function that talks to the network must be async. Rather than forcing every computation into the most general signature, the family offers a spectrum of components from the simplest (`Computer`) to the most general (`Handler`), and provides combinators that promote a provider from a simpler variant into a more capable one when the wiring needs it. A provider author writes against the weakest variant that fits, and the combinators bridge the gap. - -The unifying type signature is worth holding in mind before the variations: a handler maps `(context, Code, Input) -> Output`, where `Output` is an associated type chosen by the provider, not a generic parameter the caller fixes. Because `Output` is associated, the provider decides what it returns given a `Code` and `Input`, and downstream wiring reads that decision back out. The fallible and async variants refine this signature — wrapping `Output` in a `Result`, or returning it from a future — without changing the underlying `Code`/`Input`/`Output` correspondence. - -## The three axes of variation - -The family varies along three independent axes, and a component's name encodes its position on each. Understanding the axes makes the otherwise large set of components systematic rather than arbitrary. - -The first axis is **synchronous versus asynchronous**. A synchronous component computes and returns immediately; an asynchronous one returns a future, and its method is declared `async`. The async variants carry an `Async` in their name — `AsyncComputer` is the async counterpart of `Computer`. The most general component, `Handler`, is async by definition and so does not need the `Async` qualifier. - -The second axis is **infallible versus fallible**. An infallible component always produces its `Output`; a fallible one returns `Result` against the context's abstract error type, and so supertraits [`HasErrorType`](../reference/components/has_error_type.md). The fallible synchronous component is `TryComputer`, named with the `Try` prefix that marks the whole fallible-but-synchronous corner of the family. The fully general `Handler` is fallible as well as async. - -The third axis is **owned input versus by-reference input**. By default a handler consumes its `Input` by value, taking ownership; the `*Ref` variants instead borrow the input as `&Input`, which suits computations that only need to read their argument. Every base component has a `*Ref` sibling: `Computer` has `ComputerRef`, `AsyncComputer` has `AsyncComputerRef`, `TryComputer` has `TryComputerRef`, and `Handler` has `HandlerRef`. The `Ref` variants share the same `Code`/`Output` model and differ only in taking `&Input` where the owned variant takes `Input`. - -Combining these axes yields the components documented separately. The pure-computation corner — synchronous, infallible, in owned and by-reference forms, plus their async counterparts — is [`Computer`](../reference/components/computer.md). The fallible-but-synchronous corner is [`TryComputer`](../reference/components/try_computer.md). The fully general async-and-fallible corner is [`Handler`](../reference/components/handler.md). A fifth component, [`Producer`](../reference/components/producer.md), sits slightly apart: it is a handler with *no* input at all, producing a value from the context and a `Code` tag alone. - -## Wiring like any component - -Each handler component is an ordinary CGP component defined with `#[cgp_component]`, so it is consumed, implemented, and wired exactly like every other component. A context calls the consumer trait — `CanCompute`, `CanTryCompute`, `CanHandle`, `CanProduce`, and the rest — and a provider implements the matching provider trait — `Computer`, `TryComputer`, `Handler`, `Producer`. The context selects which provider answers a given component by delegating the component's marker (`ComputerComponent`, `HandlerComponent`, and so on) to a provider in its [`delegate_components!`](../reference/macros/delegate_components.md) table. Nothing about handlers requires special wiring machinery. - -Because the consumer methods carry a `Code` and an `Input` as type parameters, the handler components are defined to support dispatch on both. Each is declared with `#[derive_delegate(UseDelegate)]` and `#[derive_delegate(UseInputDelegate)]`, so a context can route to different providers based on the `Code` tag or on the `Input` type using an inner type-level table, in the manner described in [dispatching](dispatching.md). `UseDelegate` keys the lookup on `Code`; the family's own `UseInputDelegate` keys it on `Input`. This is what lets a single context host many independent handlers — one per `Code` — and still wire each to a different provider. - -## Promoting providers between variants - -A provider written for a simpler variant can be lifted into a more capable one automatically, which is why an author only ever implements the weakest variant that fits. The lifting follows the natural inclusions between the axes: an infallible computation is trivially a fallible one that never returns an error, a synchronous computation is trivially an async one that never awaits, and an owned-input computation can serve a by-reference caller by dereferencing the borrow. CGP encodes each of these inclusions as a combinator provider — `Promote`, `PromoteAsync`, `TryPromote`, `PromoteRef`, and the bundled `Promote*` tables — that wraps a provider of one variant and re-exposes it as another. - -The practical consequence is that wiring one provider can satisfy several handler components at once. The bundled promotion tables ([`PromoteComputer`](../reference/providers/handler_combinators.md), `PromoteProducer`, `PromoteTryComputer`, and the rest) delegate every other handler component to the appropriate single-step promoter, so delegating a context's whole handler surface to one of them makes a single underlying provider answer `CanCompute`, `CanTryCompute`, `CanComputeAsync`, `CanHandle`, and their `Ref` forms together. The full catalogue of promotion combinators, along with `ReturnInput`, `ComposeHandlers`, and `PipeHandlers`, is documented in [handler combinators](../reference/providers/handler_combinators.md). The convenience macros [`#[cgp_computer]`](../reference/macros/cgp_computer.md) and [`#[cgp_producer]`](../reference/macros/cgp_producer.md) lean on these tables: they turn a plain function into a provider for the narrowest fitting component and then wire the promotion table so the same function answers the entire family. - -## Related constructs - -The handler family builds directly on a few core constructs. The fallible variants — `TryComputer`, `Handler`, and their `Ref` forms — supertrait [`HasErrorType`](../reference/components/has_error_type.md), so their `Result` return type names the context's abstract error. The per-component documents cover each corner in detail: [`Computer`](../reference/components/computer.md) for pure synchronous and async computation, [`TryComputer`](../reference/components/try_computer.md) for fallible synchronous computation, [`Handler`](../reference/components/handler.md) for the general async-and-fallible case, and [`Producer`](../reference/components/producer.md) for the no-input case. The combinators that move providers between these components are described in [handler combinators](../reference/providers/handler_combinators.md), and the macros that generate handler providers from functions are [`#[cgp_computer]`](../reference/macros/cgp_computer.md) and [`#[cgp_producer]`](../reference/macros/cgp_producer.md). Dispatching a handler on its `Code` or `Input` uses the mechanism in [dispatching](dispatching.md), and chaining handlers into pipelines is the subject of [monadic handlers](monadic-handlers.md). - -## Source - -The handler components are defined in [crates/extra/cgp-handler/src/components/](../../crates/extra/cgp-handler/src/), with one module per corner: `computer.rs`, `async_computer.rs`, `try_compute.rs`, `handler.rs`, and `produce.rs`. The promotion combinators and the `UseInputDelegate` dispatch type live in [crates/extra/cgp-handler/src/providers/](../../crates/extra/cgp-handler/src/) and `types.rs`. The family is re-exported through `cgp::extra::handler`. Behavioral tests covering the full promotion surface are in [crates/tests/cgp-tests/tests/handlers/](../../crates/tests/cgp-tests/tests/handlers/). diff --git a/docs/concepts/higher-order-providers.md b/docs/concepts/higher-order-providers.md deleted file mode 100644 index be856d42..00000000 --- a/docs/concepts/higher-order-providers.md +++ /dev/null @@ -1,82 +0,0 @@ -# Higher-order providers - -A higher-order provider is a provider parameterized by another provider, so that part of its behavior is supplied by an inner provider it delegates to rather than fixed in its own code. - -## The idea - -A higher-order provider takes another provider as a generic parameter and builds its result on top of whatever that inner provider computes. The motivating shape is a wrapper that transforms the output of an existing implementation: a `ScaledArea` provider that multiplies whatever area an `InnerCalculator` produces, an `IterSumArea` provider that sums the areas an inner calculator returns for each element of a collection, a `GloballyScaledArea` provider that applies a context-wide factor on top of a base calculation. In each case the outer provider knows the *transformation* but not the *base case* — the base case is a parameter. - -This is the type-level counterpart to passing a function to a function. Ordinary CGP providers are already swappable behaviors selected at wiring time; a higher-order provider lets one behavior be expressed in terms of another, so the two compose. Wiring `AreaCalculatorComponent` to `ScaledArea` reads as "compute the rectangle area, then scale it," and `ScaledArea` reuses the same scaling logic over a different base. The composition happens entirely in types, with no runtime indirection, because providers carry no runtime value — they are type-level markers naming an implementation, so nesting them costs nothing. - -A higher-order provider is written like any other provider with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), with the inner provider declared as a generic parameter in the provider's `Self` position and bound to a provider trait in the `where` clause. The body invokes the inner provider as an associated function on the context. The skeleton, before the ergonomic attributes below, is a provider struct generic over an inner provider whose `where` clause requires that inner provider to implement the component's provider trait for the same context. - -## The stray `` on the inner bound - -The detail that makes higher-order providers look stranger than they are is the extra `` argument on the inner provider's bound. A provider trait, as generated by [`#[cgp_component]`](../reference/macros/cgp_component.md), moves the original `Self` into an explicit leading `Context` type parameter — `AreaCalculator` rather than the consumer trait's `CanCalculateArea`. So when a higher-order provider depends on its inner provider, the bound must name that context argument explicitly: - -```rust -#[cgp_impl(new ScaledArea)] -impl AreaCalculator -where - InnerCalculator: AreaCalculator, -{ - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - InnerCalculator::area(self) * scale_factor * scale_factor - } -} -``` - -The `` in `InnerCalculator: AreaCalculator` is exactly the leading context slot, filled with the context the outer provider is implementing for. It has no analogue in the consumer trait `CanCalculateArea`, so a reader who thinks of providers as "just the consumer trait implemented elsewhere" is caught off guard by it. The same asymmetry appears at the call site: the inner provider is invoked as the associated function `InnerCalculator::area(self)`, not as a method `self.area()`, because the provider trait's method takes the context as an explicit first argument. - -## Hiding the friction with `#[use_provider]` - -The [`#[use_provider]`](../reference/attributes/use_provider.md) attribute exists to erase the bound surprise, and is the idiomatic way to write higher-order providers. Written alongside `#[cgp_impl]`, it takes the inner bound without the context argument and fills the `` back in for you: - -```rust -#[cgp_impl(new ScaledArea)] -#[use_provider(InnerCalculator: AreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - let base_area = InnerCalculator::area(self); - base_area * scale_factor * scale_factor - } -} -``` - -The author writes `InnerCalculator: AreaCalculator` and the macro emits `InnerCalculator: AreaCalculator` into the `where` clause, so the bound reads as if the provider trait had the same shape as the consumer trait. The call-site asymmetry remains, by contrast: `#[use_provider]` completes the bound only and does not rewrite the body, so the inner provider is still invoked as the associated function `InnerCalculator::area(self)`, passing the context explicitly. That spelled-out call is the one piece of source that still reflects the provider trait's leading context argument. See [`#[use_provider]`](../reference/attributes/use_provider.md) for the exact rules. - -## `UseContext` as a default inner provider - -A higher-order provider can default its inner provider to [`UseContext`](../reference/providers/use_context.md) so that, absent an explicit choice, it falls back to whatever the context itself is already wired to. This requires giving the provider an explicit struct definition with a default generic parameter: - -```rust -pub struct IterSumArea(pub PhantomData); -``` - -`UseContext` is CGP's provider that implements a provider trait by deferring to the context's own consumer-trait implementation — the dual of the consumer blanket impl. With `IterSumArea` as the default, an unparameterized `IterSumArea` computes each inner element through the context's existing area wiring, so a higher-order provider can be dropped in without restating the base case. Overriding the parameter — `IterSumArea` — instead binds the inner behavior statically, bypassing the context's wiring, which is useful for overriding or short-circuiting what the context would otherwise resolve. The default only exists when the provider has an explicit struct with the default parameter; a provider defined purely through `#[cgp_impl(new ...)]` has no default and must always have its inner provider named. - -## Not every generic provider is higher-order - -A provider that merely has generic parameters is not a higher-order provider; it becomes one only when a parameter is constrained to implement a provider trait. The distinction matters because many providers are generic for unrelated reasons. The `UseField`-style getter provider is generic over a field tag: - -```rust -#[cgp_impl(new GetName)] -impl NameGetter -where - Self: HasField, -{ - fn name(&self) -> &str { - self.get_field(PhantomData) - } -} -``` - -Here `Tag` is a type-level field name, used only as a `HasField` key, with no provider-trait bound — so `GetName` is an ordinary parameterized provider, not a higher-order one. The defining trait of a higher-order provider is that a generic parameter appears in a provider-trait bound and is invoked to do part of the work, as `InnerCalculator: AreaCalculator` does. When that bound is absent, the `#[use_provider]` attribute has nothing to fill in, and none of the higher-order machinery applies. - -## Related constructs - -Higher-order providers are written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) (or `#[cgp_fn]`), giving the inner provider as a generic parameter in the provider's `Self` position. The [`#[use_provider]`](../reference/attributes/use_provider.md) attribute is the idiomatic tool for them: it supplies the hidden `` on the inner bound, leaving the body to invoke the inner provider as the associated-function call the provider trait actually requires. [`UseContext`](../reference/providers/use_context.md) serves as the default inner provider when the higher-order provider is given an explicit struct with a defaulted parameter, letting it fall back to the context's own wiring. For dispatching to different inner providers based on a generic type rather than naming one statically, prefer the [`open` statement](../reference/macros/delegate_components.md) of `delegate_components!`, which maps each shape to a different `ScaledArea<...>` through the `RedirectLookup` impl every component carries; the legacy [`UseDelegate`](../reference/providers/use_delegate.md) nested table does the same job for older code. Each layer of a nested provider can be verified independently with the `#[check_providers]` form of [`check_components!`](../reference/macros/check_components.md), which is what makes higher-order wiring debuggable. - -## Source - -The higher-order pattern is exercised throughout [crates/tests/cgp-tests/tests/higher_order_providers/](../../crates/tests/cgp-tests/tests/higher_order_providers/), notably [use_provider_impl.rs](../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs); `UseContext` lives in [crates/core/cgp-component/src/providers/use_context.rs](../../crates/core/cgp-component/src/providers/use_context.rs). diff --git a/docs/concepts/impl-side-dependencies.md b/docs/concepts/impl-side-dependencies.md deleted file mode 100644 index 5b326012..00000000 --- a/docs/concepts/impl-side-dependencies.md +++ /dev/null @@ -1,50 +0,0 @@ -# Impl-side dependencies - -The core idea of CGP is dependency injection through the `where` clause of a blanket impl: a clean trait interface hides the constraints its implementation needs, so a context that satisfies those constraints gains the capability without ever naming them. - -## The problem - -Generic code needs dependencies, and the usual way of declaring them — a `where` clause on a generic function — leaks those dependencies to every caller. A function `fn greet(ctx: &Context) where Context: HasName` forces its constraint onto its signature, so any generic caller of `greet` must repeat `Context: HasName`, and that caller's callers must repeat it again. The bounds propagate outward through the whole call graph, and a function deep in a library can end up demanding that its distant callers spell out requirements they have no direct interest in. - -CGP removes this leak by moving the dependency off the interface and onto an implementation. The capability is exposed as a trait whose declaration mentions none of its requirements; the requirements live only in the `where` clause of the impl that satisfies the trait. A caller bounds on the clean trait alone, and the requirements stay hidden one level down — which is precisely what stops them from cascading through transitive callers. - -## How CGP expresses it - -The mechanism is a blanket impl whose `where` clause carries the hidden constraints. A trait `CanGreet` exposes only `greet`; its blanket impl over a generic context requires `Context: HasName`, so any context implementing `HasName` automatically implements `CanGreet`, while a caller bounding on `CanGreet` never sees `HasName`. - -```rust -use cgp::prelude::*; - -pub trait CanGreet { - fn greet(&self); -} - -impl CanGreet for Context -where - Context: HasName, -{ - fn greet(&self) { - println!("Hello, {}!", self.name()); - } -} -``` - -This constraint-hiding is why a blanket impl is preferred over a generic function for the same logic: the dependency `HasName` is injected at the impl, not demanded at the interface, so transitive callers carry nothing. The same `where` clause that would bloat a function's signature becomes invisible plumbing on the impl. Writing this impl by hand is repetitive — the supertrait bounds are stated twice and each default body is copied — which is the boilerplate [`#[blanket_trait]`](../reference/macros/blanket_trait.md) removes: you declare the trait with its supertraits and default bodies, and the macro generates the matching blanket impl. - -## How it scales up - -The same `where`-clause injection appears at every level of CGP, from the simplest extension trait to full components. A [`#[blanket_trait]`](../reference/macros/blanket_trait.md) hides trait dependencies declared as supertraits; a [`#[cgp_component]`](../reference/macros/cgp_component.md) provider hides them in the `where` clause of its provider impl, where they are also captured by [`IsProviderFor`](../reference/traits/is_provider_for.md) so a missing one is reported clearly. In both cases the dependency is a constraint on the implementation that the public interface does not mention. The difference is only that a component admits many alternative providers selected by wiring, while a blanket trait admits exactly one. - -Stating those dependencies is made to read like importing them rather than constraining `Self`. The [`#[uses(...)]`](../reference/attributes/uses.md) attribute turns `#[uses(HasName)]` into a `Self: HasName` predicate on the generated impl, so a provider declares the capabilities its body calls in a line that reads like a `use` statement — the dependency lands on the impl, hidden from anyone who uses the trait. This is the same injection as a hand-written `where Self: HasName`, dressed to match a programmer's existing intuition. - -## Value dependencies, not just trait dependencies - -Impl-side dependencies inject values as readily as they inject capabilities, through the same `where`-clause mechanism keyed on fields instead of traits. A provider that needs a `name` value declares `Context: HasField` in its `where` clause and reads it with `get_field`; any context that derives [`HasField`](../reference/derives/derive_has_field.md) and happens to have a matching field satisfies the bound, with the field never appearing in any interface. The field requirement is injected at the impl exactly as a trait requirement is. - -Because the raw `HasField` bound is verbose, the surface syntax hides it the same way [`#[uses(...)]`](../reference/attributes/uses.md) hides a trait bound. A getter method written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) generates the `HasField` bound from the method name, and an [`#[implicit]`](../reference/attributes/implicit.md) function argument generates it from the argument name — both producing the same injected constraint from familiar-looking code. That value-injection model, where providers look like functions taking arguments sourced from context fields, is the subject of [implicit arguments](implicit-arguments.md). - -## Related constructs - -[`#[blanket_trait]`](../reference/macros/blanket_trait.md) is impl-side dependencies in their purest form: a trait with supertraits and default bodies, plus the generated blanket impl that hides those supertraits. [`#[cgp_component]`](../reference/macros/cgp_component.md) extends the pattern to providers, where the hidden constraints live in each provider's `where` clause and many providers can coexist — see [consumer and provider traits](consumer-and-provider-traits.md) for that duality, and [`IsProviderFor`](../reference/traits/is_provider_for.md) for how the hidden dependencies are surfaced in error messages. - -The injection is declared idiomatically through [`#[uses(...)]`](../reference/attributes/uses.md) for trait dependencies and through [`HasField`](../reference/traits/has_field.md) for value dependencies. Value injection in particular is the foundation of [implicit arguments](implicit-arguments.md), which dresses `HasField` reads as ordinary function parameters. diff --git a/docs/concepts/implicit-arguments.md b/docs/concepts/implicit-arguments.md deleted file mode 100644 index 857daf20..00000000 --- a/docs/concepts/implicit-arguments.md +++ /dev/null @@ -1,44 +0,0 @@ -# Implicit arguments - -Implicit arguments are a programming model in which a provider is written like an ordinary function taking arguments, where arguments marked `#[implicit]` are sourced not from the caller but from fields of the context. - -## The idea - -A provider almost always needs values from its context, and CGP makes asking for them look like asking for function parameters. Instead of declaring a `HasField` bound and calling `get_field` inside the body, the author writes a normal-looking parameter — `#[implicit] width: f64` — that reads as "this function needs a `width` of type `f64`." The macro removes that parameter from the public signature, adds the matching field bound, and binds the value at the top of the body, so the code keeps the shape of a plain function while behaving like a provider that pulls its inputs from the context. - -This framing is the recommended on-ramp to CGP precisely because it requires no new vocabulary. A programmer who understands functions and arguments can write a complete provider without first meeting `HasField`, type-level symbols, or `PhantomData` tags; those mechanics stay hidden behind a parameter that looks ordinary. The model defers the underlying machinery until the author has a reason to care about it. - -## How CGP expresses it - -An implicit argument is a function parameter carrying the [`#[implicit]`](../reference/attributes/implicit.md) attribute, whose name doubles as the context field name it reads from. The attribute is meaningful inside the two macros that rewrite function bodies into providers: [`#[cgp_fn]`](../reference/macros/cgp_fn.md), which turns a free function into a single-implementation capability, and [`#[cgp_impl]`](../reference/macros/cgp_impl.md), which writes a provider for an existing component. In both, the marked argument disappears from the signature and is fetched from the context instead. - -```rust -use cgp::prelude::*; - -#[cgp_fn] -pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} -``` - -The desugaring is the impl-side dependency pattern applied to values: each implicit argument becomes a [`HasField`](../reference/traits/has_field.md) bound on the generated impl and a `let` binding that reads the field at the top of the body. The function above generates a `RectangleArea` trait whose method takes no arguments, and a blanket impl requiring `HasField` and the matching `height` bound, with `width` and `height` read from the context before the body runs. The same rewrite happens identically inside a [`#[cgp_impl]`](../reference/macros/cgp_impl.md) method, where the bounds join the provider impl's `where` clause. The field bounds are satisfied by any context that derives [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) and carries fields of the matching names — no wiring is involved for `#[cgp_fn]`, since its blanket impl applies to every context that has the fields. - -Because the argument name is the field name, the values are addressed by name through the type-level symbol [`HasField`](../reference/traits/has_field.md) uses internally, and the whole `Symbol!`/`get_field`/`PhantomData` apparatus stays out of sight. What the author sees is a function with arguments; what the compiler sees is a provider with injected dependencies. - -## Access rules - -The argument type controls how the field is read, following a small set of rules so that the body receives a value of exactly the declared type. The default rule is that an owned argument type, such as `f64` or `String`, reads the field by reference and appends `.clone()`, so the body gets an owned value while the context keeps its field. The one special case worth memorizing is `&str`: an argument typed `&str` is backed by a `String` field and read with `.as_str()` rather than `.clone()`, letting the body borrow without forcing the context to store a `&str`. - -These same rules govern getter access in [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md), so an author who learns them once applies them everywhere field values are read. The shared rule of thumb is that the declared type is what the body works with, and the macro inserts whatever conversion — a clone for owned values, an `.as_str()` for `&str` — bridges it to the stored field. A few further forms exist for references, options, and slices, all documented with [`#[implicit]`](../reference/attributes/implicit.md). - -## When to use which - -An implicit argument is the default way to read a context field, and a getter trait is the exception reserved for publishing a reusable capability. Both read fields through `HasField` and share the same access rules, so the choice is not about mechanics but about what the value *is*. An implicit argument treats the value as a private input to one provider: it is bound as a local at the top of the method and used freely from there, which covers reading a field once, using it throughout a body, or declaring it on each of several methods that need it. This is the form to reach for whenever a provider simply needs a value from its context. - -A getter trait written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is worth defining only in the cases an implicit argument cannot reach. Because an implicit argument reads only from the provider's own `self` and takes a plain `&T` by reference without cloning, it covers every same-context read — even a field several providers each consume, declared as the same implicit argument in each. A getter trait earns its keep in three situations it cannot handle: the field lives on a type *other* than the provider's context, so the getter is required as a `where` bound on that type (`Request: HasBasicAuthHeader`, with no `self` field to read); the accessor must exist as a *named* capability other code depends on through `#[uses(HasName)]` or a supertrait; or the getter carries an associated type inferred from the field so the type stays abstract for callers. Everywhere else, prefer the implicit argument. The full wireable getter, [`#[cgp_getter]`](../reference/macros/cgp_getter.md), is a further step still, reserved for the advanced case of choosing the source field per context at wiring time. - -## Related constructs - -The [`#[implicit]`](../reference/attributes/implicit.md) attribute is the construct itself, usable inside [`#[cgp_fn]`](../reference/macros/cgp_fn.md) and [`#[cgp_impl]`](../reference/macros/cgp_impl.md) — the former producing a single-implementation capability with no wiring, the latter a provider for a [`#[cgp_component]`](../reference/macros/cgp_component.md). All of them desugar implicit arguments into [`HasField`](../reference/traits/has_field.md) bounds, which a context supplies by deriving [`#[derive(HasField)]`](../reference/derives/derive_has_field.md). - -For the narrow cases an implicit argument cannot reach — a field on another type, a named shared accessor, or a getter with an inferred associated type — [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is the getter-trait counterpart that shares the same `.clone()`/`.as_str()` access rules; everywhere else, a provider reading a field should prefer an implicit argument. The whole model is value-level [impl-side dependencies](impl-side-dependencies.md): an implicit argument is a context dependency injected through a `where`-clause `HasField` bound and dressed as a function parameter. diff --git a/docs/concepts/modular-error-handling.md b/docs/concepts/modular-error-handling.md deleted file mode 100644 index 5cc92dc2..00000000 --- a/docs/concepts/modular-error-handling.md +++ /dev/null @@ -1,112 +0,0 @@ -# Modular error handling - -CGP handles errors modularly by splitting error handling into three independent decisions — what the abstract error type is, how a foreign error is turned into it, and how detail is attached to it — each made by wiring rather than baked into the code that fails. - -## The problem with a fixed error type - -Generic code must be able to fail without committing to a concrete error type. A provider deep in a call graph encounters a parse error, an I/O error, or a domain rule violation, and it has to return *some* error — but it is generic over the context and cannot decide whether the application wants `anyhow::Error`, `std::io::Error`, a boxed trait object, or a hand-rolled enum. Hard-coding any of these into the provider would tie every caller to that choice, and converting between them by hand at each boundary is the boilerplate that error-handling crates exist to remove. - -CGP turns the choice of error type, and the choice of how errors are constructed, into wiring decisions that live in one place per application. The code that fails refers only to an abstract error and to the capabilities of raising and wrapping; a concrete context supplies the error type and the construction strategy when it is assembled. Swapping `anyhow` for `eyre`, or routing one source error through `From` while formatting another into a string, changes a few wiring lines and touches no provider. This is the same [coherence](coherence.md)-bypassing move CGP applies everywhere — keep the implementations open and decide locally per context — applied specifically to error handling. - -## The abstract error type - -The anchor is [`HasErrorType`](../reference/components/has_error_type.md), a one-line abstract-type component that gives a context a single shared `Error` type. Every fallible operation returns `Result` (or the alias `ErrorOf`), so generic code names the error without knowing its concrete identity: - -```rust -#[cgp_type] -pub trait HasErrorType { - type Error: Debug; -} -``` - -Centralizing the error on one trait is what lets errors compose across components. If each fallible component declared its own associated `Error`, a context bounded by several of them would face several unrelated error types with no way to unify them; by having every fallible trait supertrait `HasErrorType`, they all refer to the *same* `Self::Error`. The `Debug` bound is the only requirement the abstract error carries, enough for `.unwrap()` and logging, and it propagates to whatever concrete type a context eventually wires in. A context fixes that type either directly — `impl HasErrorType for App { type Error = anyhow::Error; }` — or, more commonly, by wiring its error-type component to a provider such as `UseType`, since `#[cgp_type]` makes `HasErrorType` an abstract-type component built on [`HasType`/`TypeProvider`](../reference/components/has_type.md). - -## Raising and wrapping as capabilities - -Two further components give the abstract error its behavior, each parameterized so a context can handle many error shapes. [`CanRaiseError`](../reference/components/can_raise_error.md) converts a concrete source error into the context's abstract error, and its companion `CanWrapError` attaches a piece of detail to an existing one. Both import the abstract error with `#[use_type(HasErrorType.Error)]`, which adds the `HasErrorType` supertrait and lets each signature name the error as the bare `Error`, so the error they produce and enrich is the context's shared error type: - -```rust -#[cgp_component(ErrorRaiser)] -#[derive_delegate(UseDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanRaiseError { - fn raise_error(error: SourceError) -> Error; -} - -#[cgp_component(ErrorWrapper)] -#[derive_delegate(UseDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanWrapError { - fn wrap_error(error: Error, detail: Detail) -> Error; -} -``` - -These two capabilities cover the everyday error-handling motions: raise a foreign error into the abstract one, and wrap context onto it as it propagates. A provider that fails writes `Context::raise_error(source)` and `Context::wrap_error(err, detail)` — both associated functions, called on the context *type*, because constructing an error is a property of the context rather than of any value in scope. Crucially, the provider states which sources it raises and which details it wraps as [impl-side dependencies](impl-side-dependencies.md) in its `where` clause, so those requirements never leak into the consumer trait a caller bounds on. A loader, for example, needs only `Context: CanRaiseError + CanWrapError` to produce and enrich an error it knows nothing concrete about. - -## Strategies as interchangeable providers - -Because raising and wrapping are components, the *strategy* for each becomes a provider a context selects, and CGP ships a family of them in `cgp-error-extra` that stay generic over the context's error type. Each captures one cross-cutting way to handle an error, independent of any particular error library, so an application composes its error handling from these parts rather than writing conversion glue. The most common are a small set worth knowing by name: - -- `RaiseFrom` raises a source error by converting it through the standard `From` trait, the default whenever the abstract error already absorbs the source. -- `ReturnError` is the identity raiser for when the source already *is* the abstract error. -- `RaiseInfallible` absorbs `core::convert::Infallible`, letting code generic over a fallible step wire uniformly even when that step cannot fail. -- `DebugError` and `DisplayError` format any `Debug` or `Display` source into a `String` and forward to the context's *own* `CanRaiseError`, reducing an open-ended set of error types to the one string case the context handles. -- `DiscardDetail` wraps by dropping the detail, and `PanicOnError` aborts instead of producing an error value. - -The full list and the exact bound each provider places on the context live in the [error providers reference](../reference/providers/error_providers.md). What matters at the concept level is that these are plain providers wired like any other, so the choice of provider is also a statement about which source errors a context accepts and how it treats them. - -## Pluggable concrete backends - -Sitting alongside the generic strategy providers are the standalone backends, each pinning the abstract error to one concrete library type and supplying the raisers and wrappers that go with it. The `cgp-error-anyhow`, `cgp-error-eyre`, and `cgp-error-std` crates each export a type-setting provider — `UseAnyhowError` sets `Self::Error` to `anyhow::Error`, for instance — together with providers like `RaiseAnyhowError` and `DebugAnyhowError` that raise and wrap into that concrete type. Wiring a backend is what makes the abstract error concrete: - -```rust -use cgp_error_anyhow::{RaiseAnyhowError, UseAnyhowError}; - -delegate_components! { - App { - ErrorTypeProviderComponent: UseAnyhowError, - ErrorRaiserComponent: RaiseAnyhowError, - } -} -``` - -The backend is opt-in and confined to these wiring lines, which is the whole point of keeping it separate. Choosing `eyre` instead is a matter of swapping `UseAnyhowError`/`RaiseAnyhowError` for their `eyre` counterparts, and no provider that calls `raise_error` changes, because none of them named the concrete type in the first place. A typical application wires a mix: a backend for the concrete error type, plus the generic strategy providers for the cross-cutting cases the backend does not cover. - -## Dispatching per source error type - -The reason `CanRaiseError` and `CanWrapError` are parameterized rather than monomorphic is that a single context usually raises several unrelated source errors, each best handled differently — and the per-type dispatch each component derives is what lets one component fan out to a provider per source. Because both carry `#[derive_delegate(UseDelegate)]`, a context can open the component and assign a provider to each source error type directly in its own table, the same per-type wiring used for [dispatching](dispatching.md) any generic parameter: - -```rust -delegate_components! { - App { - open ErrorRaiserComponent; - - @ErrorRaiserComponent.String: RaiseFrom, - @ErrorRaiserComponent.ParseIntError: DebugError, - } -} -``` - -Here a raised `String` is converted straight into the abstract error by `RaiseFrom`, while a `ParseIntError` is formatted with `Debug` and then forwarded back through the `String` entry — which `RaiseFrom` handles — so two different sources collapse into one coherent error type. The `open` statement and its `@`-path keys are the [namespace](namespaces.md)-based wiring form; the older [`UseDelegate`](../reference/providers/use_delegate.md) provider expresses the same dispatch through a separate nested table. Either way, the context decides the routing, and the formatting providers like `DebugError` compose with the converting ones rather than replacing them. - -## Application-specific error components - -Nothing about modular error handling is confined to the built-in components; an application defines its own error-raising components the same way when its errors carry domain structure. A web service, for instance, wants every raised error to carry an HTTP status code, so it declares a component that takes both a status marker and a detail, and dispatches on the pair: - -```rust -#[cgp_component(HttpErrorRaiser)] -#[use_type(HasErrorType.Error)] -pub trait CanRaiseHttpError { - fn raise_http_error(_code: Code, detail: Detail) -> Error; -} -``` - -The markers `ErrUnauthorized`, `ErrNotFound`, and the like are empty structs that map to status codes, and a provider such as `DisplayHttpError` builds the concrete application error from the code and a `Display` detail. The context then wires this custom component exactly as it wires the built-in ones — opening it and routing each detail type to a provider — so a handler deep in the request pipeline writes `Self::raise_http_error(ErrUnauthorized, "you must first login".into())` without knowing the concrete error type or how status codes are attached. This is the modular error-handling pattern carried up to the application's own vocabulary: the same split between an abstract error, a raising capability, and a wiring choice, applied to errors that are richer than a plain message. - -## Related constructs - -Modular error handling is built entirely from ordinary CGP constructs. The abstract error is [`HasErrorType`](../reference/components/has_error_type.md), an [abstract-type](abstract-types.md) component defined with [`#[cgp_type]`](../reference/macros/cgp_type.md); the raising and wrapping capabilities are [`CanRaiseError` / `CanWrapError`](../reference/components/can_raise_error.md), and the strategies that satisfy them are the [error providers](../reference/providers/error_providers.md) plus the standalone backends. A provider declares which errors it raises through [impl-side dependencies](impl-side-dependencies.md) in its `where` clause, and a context selects a strategy per source error type through the [dispatching](dispatching.md) machinery and the [`open` statement](../reference/macros/delegate_components.md) over [namespaces](namespaces.md). The whole approach is the [coherence](coherence.md)-bypassing strategy specialized to errors: keep the raising implementations overlapping and open, and let each context restore a single coherent error type by wiring. The [modular serialization](../examples/modular-serialization.md) example wires an `anyhow` backend to make its context-supplied deserializer fallible, and the [money-transfer API](../examples/money-transfer-api.md) example raises status-coded HTTP errors through an application-specific error component of exactly this shape. - -## Source - -The abstract error type and the raising and wrapping traits are defined in [crates/core/cgp-error/src/](../../crates/core/cgp-error/src/); the generic strategy providers are in [crates/extra/cgp-error-extra/src/](../../crates/extra/cgp-error-extra/src/), and the pluggable concrete backends in [crates/standalone/error/](../../crates/standalone/error/). diff --git a/docs/concepts/modularity-hierarchy.md b/docs/concepts/modularity-hierarchy.md deleted file mode 100644 index 40f26ba3..00000000 --- a/docs/concepts/modularity-hierarchy.md +++ /dev/null @@ -1,156 +0,0 @@ -# Modularity hierarchy - -CGP and vanilla Rust together form a ladder of modularity: each rung allows strictly more independent implementations of one interface than the rung below, by loosening a coherence constraint at a matching cost in syntax or coupling, so the right rung is the lowest one that still expresses what a use case needs. - -## A ladder, not a switch - -Adopting CGP is not an all-or-nothing jump from ordinary Rust traits to fully context-generic code. The same capability — say, serializing a value — can be expressed at several levels of modularity, and the levels form a gradient from "exactly one implementation, no wiring" to "any number of implementations, wired per type per provider." Each step up admits more overlapping or orphan implementations that vanilla Rust's [coherence](coherence.md) rules would reject, and each step costs something: more boilerplate, a changed interface, or tighter coupling between providers. Reading the rungs in order shows what each technique buys and what it asks for, so a use case can settle at the lowest rung that still works rather than reaching for the most powerful tool by default. - -The ladder is illustrated throughout with one running capability — serializing a value, mirroring the [modular serialization](../examples/modular-serialization.md) example — so the only thing that changes between rungs is the modularity technique, not the problem. All snippets assume `use cgp::prelude::*;`. - -## Rung 1 — one implementation per interface - -The least modular rung is a generic function or a blanket trait impl, which allows exactly one implementation of the interface it defines. A blanket impl over a generic type captures a single piece of logic that applies everywhere the bound holds, and there can be only one such impl: - -```rust -pub trait CanSerializeBytes { - fn serialize_bytes(&self, serializer: S) -> Result; -} - -impl> CanSerializeBytes for Value { - fn serialize_bytes(&self, serializer: S) -> Result { - serializer.serialize_bytes(self.as_ref()) - } -} -``` - -A blanket trait is preferred over a bare generic function because it hides the `AsRef<[u8]>` bound behind a clean interface rather than leaking it to every transitive caller — the [impl-side dependency](impl-side-dependencies.md) idea in its simplest form. The limitation is absolute, though: this is the *only* way `CanSerializeBytes` is ever implemented. There is no room for a second strategy, so this rung fits a capability that genuinely has one implementation for all types, and nothing more. - -## Rung 2 — one implementation per type - -Vanilla Rust traits climb one rung by allowing a different implementation for each type, while coherence still permits at most one implementation per type. This is the everyday Rust trait, where `Vec` and `&[u8]` can each implement `Serialize` their own way: - -```rust -impl Serialize for Vec { - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_bytes(self.as_ref()) - } -} - -impl<'a> Serialize for &'a [u8] { - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_bytes(self) - } -} -``` - -The gain over rung 1 is per-type variation; the cost is that each type needs its own explicit impl even when several share logic, and the [overlap rule](coherence.md) forbids any blanket impl that would collide. Reusable building blocks can still be factored out — both bodies above could call the rung-1 `serialize_bytes` — but the trait itself admits no alternatives: once `Serialize for Vec` is chosen, that choice is global and final. This is where Rust's coherence guarantee delivers its value and also where it starts to bind: a type gets exactly one implementation of a trait, no matter what a particular application would prefer. - -## Rung 3 — many implementations, one wiring per type - -The first CGP rung keeps the type in the `Self` position but splits the trait into a consumer/provider pair, so many overlapping implementations can coexist as named providers while each type still commits to one of them globally. Applying [`#[cgp_component]`](../reference/macros/cgp_component.md) to the trait and writing providers with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) lets `SerializeBytes` and a `Serialize`-deferring `UseSerde` both exist, overlapping freely on any type that is both `AsRef<[u8]>` and `Serialize`: - -```rust -#[cgp_component(ValueSerializer)] -pub trait CanSerialize { - fn serialize(&self, serializer: S) -> Result; -} - -#[cgp_impl(new SerializeBytes)] -impl ValueSerializer -where - Self: AsRef<[u8]>, -{ - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_bytes(self.as_ref()) - } -} -``` - -A type then picks one provider with a [`delegate_components!`](../reference/macros/delegate_components.md) entry — `Vec` becomes its own context, wiring its serializer component to `SerializeBytes`: - -```rust -delegate_components! { - Vec { - ValueSerializerComponent: SerializeBytes, - } -} -``` - -This rung's advantage is backward compatibility: the original trait is extended without changing its interface, a type can still implement it directly, and many reusable providers replace the hand-copied logic of rung 2. Its limitation is that coherence is only partly lifted. The wiring still keys on the type in the `Self` position, so `Vec` commits to one provider globally — there can be no separate wiring for a generic `Vec` that would overlap it, and the [orphan rule](coherence.md) still applies, since `delegate_components!` for `Vec` must live in a crate that owns either the trait or `Vec`. This rung suits retrofitting modular providers onto an existing trait when one global choice per type is acceptable. - -## Rung 4 — many implementations, one wiring per type per context - -The decisive CGP rung moves the type being implemented out of `Self` and into an explicit parameter, so the `Self` position names a context that owns the wiring — which lifts the orphan rule and lets each context choose providers per type independently. The trait gains a `Value` parameter, leaving `Self` free to be any application context: - -```rust -#[cgp_component(ValueSerializer)] -pub trait CanSerializeValue { - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer; -} -``` - -Now two application contexts can serialize the *same* type differently, each coherent within itself, by opening the component and keying on the value type — `AppA` encoding `Vec` as hexadecimal where `AppB` uses base64: - -```rust -delegate_components! { - AppA { - open ValueSerializerComponent; - @ValueSerializerComponent.Vec: SerializeHex, - } -} - -delegate_components! { - AppB { - open ValueSerializerComponent; - @ValueSerializerComponent.Vec: SerializeBase64, - } -} -``` - -This rung nearly eliminates the coherence restrictions. Because the wiring keys on the context rather than on `Vec`, a crate that owns neither the trait nor `Vec` can still wire a serializer for `Vec` as long as it owns the context, so the orphan rule no longer bites and overlapping providers coexist without any global commitment. The cost is that the trait must be designed with the extra context parameter from the start — it cannot be retrofitted onto an existing trait like `serde::Serialize` without a breaking change — and wiring must be spelled out for every value type a context uses. This is the rung most idiomatic CGP code lives on, and the one the [modular serialization](../examples/modular-serialization.md) and [money-transfer API](../examples/money-transfer-api.md) examples build on; the per-type dispatch it relies on is the subject of [dispatching](dispatching.md), wired through the [`open` statement](../reference/macros/delegate_components.md) over [namespaces](namespaces.md). - -## Rung 5 — many implementations, wiring per type per provider - -The top rung lets one provider override the wiring of a nested type locally, without routing that choice back through the context, by taking the inner provider as a parameter — a [higher-order provider](higher-order-providers.md). A recursive serializer for a collection ordinarily asks the context how to serialize each element; a higher-order variant instead accepts an explicit element serializer, defaulting to the context only when none is given: - -```rust -pub struct SerializeIteratorWith(pub PhantomData); - -#[cgp_impl(SerializeIteratorWith)] -impl ValueSerializer -where - for<'a> &'a Value: IntoIterator, - Provider: for<'a> ValueSerializer::Item>, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer, - { /* serialize each item through `Provider` */ } -} -``` - -With this in hand a context can fix the element encoding for one collection while leaving others to the context's general wiring — serializing a `Vec>` whose inner byte vectors are hexadecimal even though plain `Vec` elsewhere is encoded as raw bytes: - -```rust -delegate_components! { - AppA { - open ValueSerializerComponent; - @ValueSerializerComponent.Vec: SerializeBytes, - @ValueSerializerComponent.Vec>: SerializeIteratorWith, - @ValueSerializerComponent.Vec: SerializeIteratorWith, - } -} -``` - -This rung adds fine-grained, per-provider control on top of rung 4's per-context control: the `Vec` entry omits the parameter and so still routes its elements through the context, while the `Vec>` entry pins its inner encoding to `SerializeHex` regardless of how the context serializes `Vec` on its own. The `UseContext` default is what makes both forms read the same; the [`UseContext` provider](../reference/providers/use_context.md) routes back to the context's wiring when no override is supplied. The cost is the extra coupling and the higher-order machinery, which is why this rung is reserved for the cases where local override genuinely matters rather than used by default. - -## Choosing a rung - -The guiding rule is to settle at the lowest rung that expresses the use case, because each step up trades simplicity for modularity that may not be needed. A capability with one universal implementation belongs on rung 1; one that varies by type but never by application belongs on rung 2; a trait that should gain alternative providers without changing its interface belongs on rung 3; a capability where different applications must encode the same type differently belongs on rung 4, the home of most CGP code; and only a provider that must override a nested type's wiring locally needs rung 5. Climbing higher than necessary adds context parameters, wiring, and coupling that buy nothing, while stopping too low forces the hand-written impls and global commitments the higher rungs exist to avoid. - -## Related constructs - -The mechanism that makes rungs 3 through 5 possible — splitting a trait into a [consumer and provider trait](consumer-and-provider-traits.md) so overlapping and orphan implementations become legal — is the subject of [bypassing coherence](coherence.md), and the dependency threading every rung relies on is [impl-side dependencies](impl-side-dependencies.md). The constructs the rungs introduce are [`#[cgp_component]`](../reference/macros/cgp_component.md) and [`#[cgp_impl]`](../reference/macros/cgp_impl.md) for the trait split, [`delegate_components!`](../reference/macros/delegate_components.md) for the wiring, the [`open` statement](../reference/macros/delegate_components.md) over [namespaces](namespaces.md) and the [dispatching](dispatching.md) idea for per-type selection, and [higher-order providers](higher-order-providers.md) with the [`UseContext` provider](../reference/providers/use_context.md) for the top rung. The whole progression is worked through on a real capability in the [modular serialization](../examples/modular-serialization.md) example. diff --git a/docs/concepts/monadic-handlers.md b/docs/concepts/monadic-handlers.md deleted file mode 100644 index 1703d2de..00000000 --- a/docs/concepts/monadic-handlers.md +++ /dev/null @@ -1,47 +0,0 @@ -# Monadic handlers - -Monadic handlers are a way to chain CGP handlers into a pipeline that automatically short-circuits when an intermediate step produces a result the chain should stop on, threading a single `Output` value through the whole sequence. - -## Purpose - -The monad layer solves the problem of sequencing handlers when some of those handlers produce a result that should end the pipeline early. Plain handler composition with [`ComposeHandlers`](../reference/providers/handler_combinators.md) and `PipeHandlers` feeds the output of each provider straight into the next, which is the right behavior only when every step always wants to continue. The moment a step can yield a value that means "stop here, this is the final answer," straight composition is wrong: the later steps would run on a value they were never meant to see. The classic case is a `Result`, where an `Err` should abandon the rest of the pipeline and become the output directly, but the same shape appears whenever a step's output type carries two possibilities — one to keep going with, one to return immediately. - -A monad captures exactly that branching. It describes, for a given output type, which case threads forward into the next handler and which case short-circuits out as the final result, plus how to lift a plain value back into that output type. With the monad chosen, a list of handlers can be composed so that each handler runs only on the "continue" branch of the previous one, and any "stop" value flows untouched to the end. The handler that the monad layer builds is itself an ordinary provider for the [`Computer`](../reference/components/computer.md) family, so a monadic pipeline plugs into the same wiring as any other handler. - -CGP ships three monads. The *identity* monad threads every value forward and never short-circuits, recovering plain composition. The *ok* monad short-circuits on `Ok` and continues on `Err`. The *err* monad short-circuits on `Err` and continues on `Ok` — the familiar `?`-style early return where the first error wins. These can also be stacked, so that a pipeline over a nested `Result, F>` can short-circuit on the outer error while threading the inner result. - -## Behavior - -A monadic pipeline runs a list of handlers left to right, where each handler is invoked only on the branch of the previous handler's output that the monad designates as "continue." The first handler receives the pipeline's input. Its output is split by the monad: the continue case is fed as the input to the second handler, and the short-circuit case is lifted directly into the pipeline's final output type, skipping every remaining handler. This repeats down the list, so the pipeline's result is either the short-circuit value of whichever handler first produced one, or the output of the final handler if none short-circuited. - -The err monad makes this concrete. Composing three handlers that each return `Result` under the err monad runs the first handler on the input; if it returns `Ok`, the contained value is passed to the second handler, and so on; if any handler returns `Err`, that error becomes the pipeline's output immediately and the remaining handlers do not run. This is the type-level equivalent of writing each step with a `?` operator. The ok monad is the mirror image: it continues on `Err` and stops on `Ok`, which is useful for pipelines that retry or fall through until something succeeds. - -The whole construction lives in types. The monad is a zero-sized marker, the handler list is a [type-level list](../reference/types/cons.md), and the pipeline provider that results carries no runtime value — it is assembled by the trait machinery at compile time, so chaining handlers monadically costs nothing at runtime beyond the branches the logic actually requires. - -## Examples - -The entry point is the [`PipeMonadic`](../reference/providers/monad_providers.md) provider, which takes a monad marker and a `Product!` list of handler providers. Given an `Increment` computer that returns `Result` — incrementing on success and reporting `"overflow"` as the error — composing three copies under the err monad runs them in sequence and stops at the first overflow: - -```rust -PipeMonadic::::compute(&context, code, 253) -// 253 -> Ok(254) -> Ok(255) -> Err("overflow") -``` - -Each `Increment` runs on the `Ok` value of the one before it; the third increment overflows, so its `Err("overflow")` becomes the pipeline's output and no further step runs. Wiring the same list under [`IdentMonadic`](../reference/providers/monad_providers.md) instead would thread the `Result` value forward unchanged, never short-circuiting — which is plain composition and is rarely what a `Result`-producing chain wants. - -Stacking monads handles nested results. A pipeline of handlers returning `Result, &str>` can short-circuit on the outer `&str` error while threading the inner `Result<(), u8>` by composing under `OkMonadicTrans` — the err monad applied as the base, transformed by the ok monad on top: - -```rust -PipeMonadic::, Product![ReturnOkErr, ReturnOkOk, ReturnOkErr]> - ::compute(&context, code, 1) -``` - -Here an outer `Err` ends the pipeline immediately, while an outer `Ok` unwraps to the inner `Result` that the next handler continues from. - -## Related constructs - -Monadic handlers build directly on the handler family: the pipelines they produce are providers for [`Computer`](../reference/components/computer.md) and its async and fallible relatives, so the same providers that a monadic pipeline composes are the providers any handler wiring uses. [`PipeMonadic`](../reference/providers/monad_providers.md) is the user-facing provider, and the monad markers `IdentMonadic`, `OkMonadic`, and `ErrMonadic` — together with their transformer forms and the per-step `BindOk` / `BindErr` providers — are documented there. The trait layer that defines what a monad *is* (which branch continues, which short-circuits, and how to lift a value) lives in [the monad traits](../reference/traits/monad.md): `MonadicTrans`, `MonadicBind`, `LiftValue`, and `ContainsValue`. The non-monadic composition that monadic pipelines generalize — straight `ComposeHandlers` and `PipeHandlers`, plus the `TryPromote` bridge between fallible and infallible handlers — is covered in [handler combinators](../reference/providers/handler_combinators.md). For dispatching to different handlers by a type-level key rather than running them in sequence, see [dispatch combinators](../reference/providers/dispatch_combinators.md). - -## Source - -The monad layer lives in [crates/extra/cgp-monad/src/](../../crates/extra/cgp-monad/src/), re-exported as `cgp::extra::monad`. The pipeline provider is in `providers/pipe_monadic.rs`, the monad markers in `monadic/{ident,ok,err}.rs`, and the trait layer in `traits/`. The behavior described here is exercised by the tests in [crates/tests/cgp-tests/tests/monadic_handlers/](../../crates/tests/cgp-tests/tests/monadic_handlers/) (`ok_monadic.rs`, `err_monadic.rs`, `ok_err_monadic_trans.rs`). diff --git a/docs/concepts/namespaces.md b/docs/concepts/namespaces.md deleted file mode 100644 index 738a4686..00000000 --- a/docs/concepts/namespaces.md +++ /dev/null @@ -1,89 +0,0 @@ -# Namespaces - -A namespace is a reusable, named lookup table of component wirings that a context can inherit wholesale and then selectively override, giving CGP its preset-style configuration without any separate preset construct. - -## The idea - -A namespace lifts a delegation table out of any one context, gives it a name, and lets other contexts inherit it. With [`delegate_components!`](../reference/macros/delegate_components.md) alone, every context spells out its own wiring entry by entry, and two contexts that should share the same set of providers must repeat it. A namespace captures "this exact set of wirings" as a thing contexts can refer to: a context says "use everything in this namespace" and gets the whole group at once. It is the answer to the question "how do I reuse a block of wiring across many contexts," and it is defined with [`cgp_namespace!`](../reference/macros/cgp_namespace.md). - -The defining feature is inheritance with override. A context that joins a namespace inherits its entries as defaults, then adds its own entries that win over the inherited ones — because a directly-wired entry on the context resolves before the namespace fallback is consulted. So a namespace behaves like a base configuration that contexts specialize: most of the wiring comes for free, and each context tweaks the handful of entries it cares about. Namespaces can also inherit from one another, so a base namespace can be extended into a richer one that every context downstream picks up. - -Crucially, a namespace is *not* a context. It is a trait — named after the namespace — that carries a `Delegate` associated type and is implemented once per key. A context opts in and forwards its lookups through that trait, so the namespace supplies defaults without ever being instantiated or holding any wiring of its own at the context level. - -## Path-based redirection - -What lets one namespace inherit from another, and lets a context shadow a single inherited entry without disturbing the rest, is that the forwarding is keyed by a *path* rather than a bare component name. A path is a type-level list of symbols and component names — written with the `@` sigil as a dotted sequence like `@MyFooComponent`, `@app.ErrorRaiserComponent`, or `@cgp.core.error` — and each namespace entry redirects a key along such a path instead of naming a provider outright. The redirection is carried by the [`RedirectLookup`](../reference/providers/redirect_lookup.md) provider, which resolves a key by walking the given path inside whatever table it is handed: - -```rust -cgp_namespace! { - new MyNamespace { - FooProviderComponent => - @MyFooComponent, - } -} -``` - -This says that when `MyNamespace` is asked for `FooProviderComponent`, it should look up the path `MyFooComponent` rather than resolve to a fixed provider — the actual provider is decided wherever the path eventually lands. Because lookups are paths rather than flat keys, a parent namespace's entire subtree can be rerouted at once (`@cgp.core.error => @app` redirects everything under that prefix), and a child context can introduce a more specific path that takes precedence over an inherited one. Under the hood every `@` path desugars into a [`PathCons`](../reference/types/path_cons.md) type-level list built by the [`Path!`](../reference/macros/path.md) macro, with lowercase dotted segments becoming `Symbol` string literals and capitalized segments becoming named types; the `=>` entries become `RedirectLookup` impls while plain `:` entries map a key straight to a provider as in `delegate_components!`. - -## Per-component dispatch with `open` - -The most common place a path appears is the `open` statement, which uses path-based redirection to dispatch a single component on its generic parameter — inline, in a context's own table. Writing `open ValueSerializerComponent;` at the head of a [`delegate_components!`](../reference/macros/delegate_components.md) block redirects that component's lookup along a path rooted at the component name into the context's own table; the per-value entries that follow are then ordinary `@`-path keys pointing into that route: - -```rust -delegate_components! { - AppA { - open ValueSerializerComponent; - - @ValueSerializerComponent.Vec: - SerializeHex, - @ValueSerializerComponent.DateTime: - SerializeRfc3339Date, - } -} -``` - -Each `@ValueSerializerComponent.Vec: SerializeHex` entry maps one value of the component's dispatch parameter — here the `Vec` serialized type — to the provider that handles it, and the [`RedirectLookup`](../reference/providers/redirect_lookup.md) impl every component carries appends that parameter onto the redirect path to find the entry. The effect is the per-type dispatch that the legacy `UseDelegate` nested table also provides, but with the entries living on the context rather than in a separate table type, so no `UseDelegate` wrapper or `#[derive_delegate]` attribute is involved. This is the form the [modular serialization](../examples/modular-serialization.md) example uses throughout. - -`open` is a lightweight form of the full namespace machinery, suited to small applications and self-contained examples where a single context wires its own components directly. It roots a component's route at the bare component name and adds the per-value entries to that context's own table, without joining any shared namespace. The full namespace feature is what scales to a large code base: a library registers each component into a shared namespace under a path prefix with the `#[prefix(...)]` attribute, and many contexts join that namespace with a `namespace …;` header to inherit the whole bundle of wirings at once — the inherit-and-override pattern the rest of this page describes. - -The two forms do not combine for the same component. When a context joins a namespace in which a component carries a prefix, that component's lookups are already routed under the prefix path, so `open`-ing it — which would root the route at the bare component name — no longer reaches those entries. The per-value entries must instead be written with the full prefixed path, `@prefix.SomeComponent.Key: Provider`, exactly as the override entries shown later do. So `open` is the convenience for a component wired directly on a context; once a component lives behind a namespace prefix, the full path is what reaches it. - -## Attaching components and joining namespaces - -A namespace is consumed from two sides: components register themselves into it, and contexts join it. A component attaches to a namespace through the `#[prefix(...)]` attribute on its trait, which emits one extra impl registering the component into the named namespace under a path prefix. CGP's own [`HasErrorType`](../reference/components/has_error_type.md), for instance, carries `#[prefix(@cgp.core.error in DefaultNamespace)]`, placing it into the built-in `DefaultNamespace` under the `cgp.core.error` prefix so any context joining that namespace inherits the standard error wiring. A context joins a namespace inside [`delegate_components!`](../reference/macros/delegate_components.md) with a `namespace` header line, after which every lookup it cannot resolve directly forwards through the namespace: - -```rust -delegate_components! { - AppA { - namespace DefaultNamespace; - - @test.ShowImplComponent.u64: - ShowWithDisplay, // a direct entry overriding the namespace default - } -} -``` - -The `namespace DefaultNamespace;` line makes `AppA` fall back to `DefaultNamespace`'s entries, while the direct line on the same context shadows just the `u64` entry — the override-by-precedence rule in action. Joining through [`delegate_and_check_components!`](../reference/macros/delegate_and_check_components.md) instead does the same while verifying the merged wiring. - -## Preset-style configuration - -Namespaces are how CGP expresses presets, and there is no separate `cgp_preset!` macro — a preset *is* a namespace. The pattern a preset library would offer elsewhere, "a curated bundle of defaults you adopt and then customize," is exactly the inherit-wholesale-and-override behavior namespaces provide. A library publishes a namespace of sensible default wirings, possibly extended from a base namespace; an application joins it, inherits the bundle, and overrides the few entries specific to its needs: - -```rust -cgp_namespace! { - new ExtendedNamespace: DefaultNamespace { - @cgp.core.error => - @app, - } -} -``` - -`ExtendedNamespace` inherits everything `DefaultNamespace` resolves and additionally reroutes the `@cgp.core.error` subtree to `@app`, so any context joining `ExtendedNamespace` gets the merged result. Because this is all expressed through trait resolution and type-level paths, the configuration has no runtime cost: the inheritance, the overrides, and the redirections are resolved entirely at compile time, and a preset is just one more namespace in the chain. - -## Related constructs - -Namespaces are defined with [`cgp_namespace!`](../reference/macros/cgp_namespace.md), whose `#[prefix(...)]` attribute (on a [`#[cgp_component]`](../reference/macros/cgp_component.md) trait) registers a component into a namespace and whose entries are resolved through the [`RedirectLookup`](../reference/providers/redirect_lookup.md) provider. Inheritance and per-type default lookups go through the [`DefaultNamespace` / `DefaultImpls` traits](../reference/traits/default_namespace.md) in `cgp-component`. Every `@` path desugars into a [`PathCons`](../reference/types/path_cons.md) type-level list built by the [`Path!`](../reference/macros/path.md) macro. A context joins a namespace inside [`delegate_components!`](../reference/macros/delegate_components.md) via its `namespace` header, or [`delegate_and_check_components!`](../reference/macros/delegate_and_check_components.md) to join and verify at once; the underlying per-key table that `RedirectLookup` walks is [`DelegateComponent`](../reference/traits/delegate_component.md). There is no `cgp_preset!` macro — presets are expressed entirely through namespaces. - -## Source - -The macro is in [crates/macros/cgp-macro-core/src/types/namespace/](../../crates/macros/cgp-macro-core/src/types/namespace/), with the `#[prefix(...)]` attribute in [attributes/prefix.rs](../../crates/macros/cgp-macro-core/src/types/attributes/prefix.rs). The runtime `DefaultNamespace`/`DefaultImpls1`/`DefaultImpls2` traits are in [crates/core/cgp-component/src/namespaces.rs](../../crates/core/cgp-component/src/namespaces.rs) and `RedirectLookup` in [crates/core/cgp-component/src/providers/redirect_lookup.rs](../../crates/core/cgp-component/src/providers/redirect_lookup.rs); expansion snapshots are in [crates/tests/cgp-tests/tests/namespaces/](../../crates/tests/cgp-tests/tests/namespaces/). diff --git a/docs/concepts/send-bounds.md b/docs/concepts/send-bounds.md deleted file mode 100644 index 4a5c8744..00000000 --- a/docs/concepts/send-bounds.md +++ /dev/null @@ -1,123 +0,0 @@ -# Recovering `Send` bounds - -Recovering a `Send` bound is the pattern of restoring the "the returned future is `Send`" guarantee that an async trait method drops, by re-declaring the method on a concrete context where the compiler can prove the bound itself — a hand-written stand-in for the Return Type Notation that stable Rust does not yet offer. - -## Why async trait methods lose their `Send` bound - -An async CGP method advertises a future whose auto-traits the caller cannot name. The standard way to declare an asynchronous component is to write an `async fn` in the consumer trait and let [`#[async_trait]`](../reference/macros/async_trait.md) rewrite it into a return-position `impl Future`: - -```rust -#[cgp_component(ApiHandler)] -#[async_trait] -#[derive_delegate(UseDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanHandleApi { - type Request; - type Response; - - async fn handle_api( - &self, - _api: PhantomData, - request: Self::Request, - ) -> Result; -} -``` - -That rewrite is faithful and zero-cost, but it drops every auto-trait bound. `handle_api` becomes `fn handle_api(..) -> impl Future>`, with no boxing and no implicit bounds — and no `Send`. The future is `Send` only when the concrete future the body produces happens to be, and a caller working through the trait has no way to *say* "I need it to be." The opacity that makes return-position `impl Trait` zero-cost is exactly what hides the future's auto-traits behind the trait boundary. - -## Why spawning needs `Send`, and why you cannot ask for it - -The bound becomes load-bearing the moment the future is spawned onto a multi-threaded executor. A work-stealing runtime — the default Tokio runtime that an Axum server runs on, for instance — may migrate a task between threads while it is suspended, so every future it drives must be `Send`. When a generic handler routes a request through `handle_api`, the surrounding task captures and awaits that future, and the task is `Send` only if the future is. For a *concrete* context the compiler can check this directly; for a context left generic it cannot, because the future's `Send`-ness is precisely the fact the trait refuses to expose. - -The bound you want to write does not exist on stable Rust. Conceptually the requirement is "for whatever arguments `handle_api` is called with, its future is `Send`", and Rust has a notation for exactly that — Return Type Notation (RTN), which lets a bound name a method's return type: - -```rust -// Not available on stable Rust: -fn spawn_handler(app: App) -where - App: CanHandleApi + Send + 'static, -{ - tokio::spawn(async move { /* ... app.handle_api(...).await ... */ }); -} -``` - -The `handle_api(..): Send` clause says the future returned by the method is `Send` no matter the arguments, which is what a work-stealing spawn demands. RTN is not stabilized, however, so this bound cannot be written in production code today, and a generic caller is left unable to express the one requirement the executor imposes. - -## Recovering the bound with a concrete `Send` trait - -The workaround is to declare a second, ordinary trait whose method states the `Send` bound directly in its return type, sidestepping RTN entirely: - -```rust -pub trait CanHandleApiSend: - CanHandleApi + Send + Sync -{ - fn handle_api_send( - &self, - _api: PhantomData, - request: Self::Request, - ) -> impl Future> + Send; -} -``` - -`CanHandleApiSend` is a plain trait, not a [component](../reference/macros/cgp_component.md) — it adds nothing to the wiring and exists only to carry stronger bounds. It inherits the full capability from `CanHandleApi` as a supertrait, additionally requiring the request and response to be `Send` and the context itself to be `Send + Sync`, and it spells out `+ Send` on the future explicitly rather than through a clause on `handle_api`. A caller that holds `App: CanHandleApiSend` therefore knows the future is `Send` from the signature alone, with no RTN in sight. This is the bound a spawning handler can finally name: - -```rust -where - App: CanHandleApiSend, -``` - -## Why the implementation must be concrete - -The recovered trait cannot be implemented once, generically — that would require the very bound RTN is missing. The natural impl to reach for is a blanket one over every context that already handles the API: - -```rust -// Does not compile on stable Rust: -impl CanHandleApiSend for App -where - App: CanHandleApi + Send + Sync, -{ - async fn handle_api_send( - &self, - api: PhantomData, - request: Self::Request, - ) -> Result { - self.handle_api(api, request).await - } -} -``` - -The body wraps `self.handle_api(..)` in an `async` block, and that block is `Send` only if the future it awaits is `Send`. For a generic `App` and `Api` the awaited future is an opaque `impl Future` whose auto-traits are unknown — the same gap restated — so the impl fails to prove its own `+ Send` return type. A generic blanket impl is just RTN wearing a disguise, and it is blocked for the same reason. - -Dropping to a concrete context and a concrete API closes the gap. When `Self` is a fixed type and `Api` is a fixed marker, `self.handle_api(api, request)` resolves through the wiring to a concrete provider stack producing a concrete future, and the compiler computes that future's auto-traits and finds it `Send` — no annotation required, because `Send` is inferred structurally for a known type. The impl is therefore written per concrete `(context, API)` pair: - -```rust -impl CanHandleApiSend for MockApp { - async fn handle_api_send( - &self, - api: PhantomData, - request: Self::Request, - ) -> Result { - self.handle_api(api, request).await - } -} - -impl CanHandleApiSend for MockApp { - async fn handle_api_send( - &self, - api: PhantomData, - request: Self::Request, - ) -> Result { - self.handle_api(api, request).await - } -} -``` - -Each impl is mechanical — it forwards to `handle_api` and awaits — yet each is also a proof, accepted only because at this concrete instantiation the future really is `Send`. The repetition is the cost of the missing notation: one concrete impl per API per context replaces the single generic impl RTN would have allowed. The [money-transfer API example](../examples/money-transfer-api.md) shows the pattern in its place, with `MockApp` recovering the bound so its handlers can be served by Axum. - -## Related constructs - -The dropped bound originates with [`#[async_trait]`](../reference/macros/async_trait.md), whose Known issues section records the same `Send`-less future from the macro's side. The recovered trait sits atop a [component](../reference/macros/cgp_component.md) defined the usual way and consumed through the [consumer/provider duality](consumer-and-provider-traits.md); the [`Handler`](../reference/components/handler.md) family is the built-in async component most likely to need this treatment when its futures are spawned. The concrete impls forward through whatever provider stack the context wires with [`delegate_components!`](../reference/macros/delegate_components.md), and the [higher-order providers](higher-order-providers.md) in that stack are part of what makes the resolved future a concrete, checkable type. - -## Source - -The behavior this pattern compensates for lives in the `#[async_trait]` rewrite at [crates/macros/cgp-async-macro/src/impl_async.rs](../../crates/macros/cgp-async-macro/src/impl_async.rs), which produces a bare `-> impl Future` with no auto-trait bounds. The recovery itself is application-level rather than a CGP construct: it is an ordinary trait whose method declares `+ Send` on its return type and whose impls are written for concrete contexts, so there is no macro or core trait that implements it — only the language feature, Return Type Notation, that would make it unnecessary. diff --git a/docs/concepts/type-level-dsls.md b/docs/concepts/type-level-dsls.md deleted file mode 100644 index 6f4c3d36..00000000 --- a/docs/concepts/type-level-dsls.md +++ /dev/null @@ -1,97 +0,0 @@ -# Type-level DSLs - -A type-level DSL encodes a small language as Rust *types* and interprets those types at compile time through CGP wiring, so that a program's syntax is decoupled from its semantics and each can vary independently. - -## The idea - -The pattern is to represent a program as a type rather than a value, and to make interpreting that program a matter of resolving a CGP component for it. A program fragment becomes a phantom struct that carries no data — it names an operation — and the meaning of that operation is supplied by whichever provider a context wires for it. Because the program is a type, the Rust trait system *is* the interpreter: type checking the call that runs the program is what selects and composes the per-fragment implementations. There is no runtime parser, no abstract-syntax-tree walk, and no dispatch loop; the whole interpretation collapses into trait resolution that the compiler performs once. - -What makes this worth doing in CGP specifically is the same property that motivates CGP everywhere else: the separation of an interface from its implementations, lifted here to the separation of *syntax* from *semantics*. A fragment of syntax like a "run this command" marker says nothing about how the command runs — on which async runtime, against which error type, producing which output type. All of that is decided by the provider the context binds to that fragment, so a single program runs differently under different contexts, and a context can replace one fragment's interpreter without disturbing any program that uses it. This is the type-level analogue of a tagless-final or finally-encoded interpreter, but with CGP's extra `Context` parameter threading dependency injection through every fragment. - -The pattern composes from constructs documented individually elsewhere; the [shell-scripting DSL example](../examples/shell-scripting-dsl.md) develops a complete instance end to end, and this document explains the shape those pieces form. The running illustrations here use that example's vocabulary — `SimpleExec`, `Pipe`, the `Handler` component — so that the concepts and the worked code share one set of names. - -## Abstract syntax as phantom types - -Each piece of the language's grammar is a zero-data struct whose type parameters carry the program's structure. A "run a command" fragment is nothing more than its phantom marker: - -```rust -pub struct SimpleExec(pub PhantomData<(Path, Args)>); -``` - -The struct has no methods and no interpreter attached — it exists only to be named in a type and matched on later. Its parameters are themselves type-level data: a command path, an argument list, a request method. Composite fragments nest the same way, so a pipeline of stages is a single type built from a [`Product!`](../reference/macros/product.md) type-level list of stage types, and a literal string inside a program becomes a [`Symbol!`](../reference/macros/symbol.md) type-level string because a `&str` value cannot appear where only types are allowed. A whole program is therefore one large type assembled from these markers — the language's *abstract syntax*, deliberately holding no information about how any fragment behaves. - -## A computation component as the interpreter interface - -The interpreter is a single CGP component whose `Code` type parameter is the program fragment being run. The [handler family](handlers.md) is built for exactly this: each component threads a phantom `Code` tag alongside an `Input`, and produces an associated `Output`. The general member, [`Handler`](../reference/components/handler.md), is async and fallible: - -```rust -#[async_trait] -#[cgp_component(Handler)] -#[derive_delegate(UseDelegate)] -#[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanHandle { - type Output; - - async fn handle( - &self, - _tag: PhantomData, - input: Input, - ) -> Result; -} -``` - -Running a program is then one call to the consumer method, passing the program type as `PhantomData`. The `Code` tag carries no value precisely so that one context can host an interpreter for every fragment in the language, each keyed by a distinct `Code` type, and so that the wiring can dispatch on it. A DSL whose fragments never fail or never await can use a simpler member of the family — [`Computer`](../reference/components/computer.md) for pure synchronous fragments — and rely on the family's promotion combinators to lift it where a more capable interpreter is required; the choice of component is the choice of what capabilities the language's fragments are allowed to have. - -## Providers as interpreters - -A provider interprets one fragment by pattern-matching on the `Code` parameter through its generic arguments. Written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), an interpreter reads like an ordinary method body while the context stays generic, and it states everything the fragment needs from the context as [impl-side dependencies](impl-side-dependencies.md) — capability bounds through [`#[uses(...)]`](../reference/attributes/uses.md) and the remaining structural bounds in the `where` clause: - -```rust -#[cgp_impl(new HandleStreamChecksum)] -#[uses(CanRaiseError)] -#[use_type(HasErrorType.Error)] -impl Handler, Input> -where - Input: Unpin + TryStream, - Hasher: Digest, - Input::Ok: AsRef<[u8]>, -{ - type Output = GenericArray; - - async fn handle(/* ... */) -> Result { - /* fold the stream into a digest */ - } -} -``` - -Matching `Handler, …>` for any `Hasher` lets the one provider cover an entire family of fragments, and because the provider never names a concrete error or runtime type it interprets the same fragment under any context that can satisfy its bounds. This is where syntax and semantics finally meet: the fragment `Checksum` says *what*, the provider says *how*, and the binding between them is made only at wiring time. Swapping the provider re-interprets every program that uses the fragment, without editing a single program. - -## Assembling an interpreter by dispatching on the syntax - -A whole-language interpreter is assembled by routing each `Code` to its own provider, which is what [`UseDelegate`](../reference/providers/use_delegate.md) does. Because the handler components are declared `#[derive_delegate(UseDelegate)]`, a context can resolve the handler component through an inner type-level table keyed on the `Code` type, in the manner described in [dispatching](dispatching.md). Each entry maps a fragment type — its generic parameters bound by a leading `<…>` so the key matches every use — to the provider that interprets it: - -```rust -@HandlerComponent. SimpleExec: - HandleSimpleExec, -@HandlerComponent. SimpleHttpRequest: - HandleSimpleHttpRequest, -``` - -Resolving the interpreter for a program then walks one extra step through this table: the context dispatches the handler component on the fragment type and finds its provider. Because the keys are types rather than values, a single entry can capture generic structure that a value-level lookup table never could, and the per-fragment providers stay completely independent of one another — adding a fragment is adding a row. - -## Composing and extending the language - -Two further constructs turn a set of fragment interpreters into a real, extensible language. Composition of fragments is itself interpreted by a provider: a pipeline fragment is wired to a combinator like [`PipeHandlers`](../reference/providers/handler_combinators.md), which threads the output of one stage's interpreter into the input of the next, so that a compound program is interpreted by composing the interpreters of its parts. Extensibility comes from [namespaces](namespaces.md): the table of fragment-to-provider wirings is captured once with [`cgp_namespace!`](../reference/macros/cgp_namespace.md), and a context joins it through [`delegate_components!`](../reference/macros/delegate_components.md) to inherit the whole language at once. A language *extension* is then a new namespace that inherits the base and adds rows for new fragments and their interpreters — an independent crate can extend the DSL without patching, forking, or even depending on the parts of the core it does not use. This is the type-level counterpart of an interpreter that anyone can add cases to from outside. - -## A surface-syntax layer - -A procedural macro can sit on top of the abstract syntax to give the language a readable surface, but it is a strictly optional convenience. Such a macro performs only shallow token rewriting — turning an infix pipe operator into a nested pipeline type, a bracketed list into a `Product!`, a string literal into a `Symbol!` — and emits the same plain type a programmer could write by hand. Because the surface macro produces ordinary types and never touches interpretation, it carries no semantics of its own: the language remains fully usable, and fully extensible, without it. Keeping the surface layer this thin is what lets the *grammar* evolve independently of both the macro and the interpreters. - -## Tradeoffs - -Interpreting at compile time is the pattern's strength and its limit at once. The payoff is that a program runs at native speed with no interpreter overhead, that the Rust type system checks a program's well-formedness before it ever runs, and that the same program retargets to a different context for free. The cost is that a program must be known at compile time, so the technique does not suit languages loaded dynamically at runtime — configuration read from a file, user-supplied scripts, plugins — without also shipping a compiler. The other practical cost is diagnostics: a type error in one fragment surfaces as a trait-resolution failure that can be verbose and indirect, which the [check traits](check-traits.md) help localize during development but do not entirely tame. The pattern fits best where the language is fixed at build time and the value of zero-cost, type-checked, retargetable programs outweighs the friction of compile-time error messages. - -## Related constructs - -A type-level DSL is an assembly of constructs documented on their own. The interpreter interface is a member of the [handler family](handlers.md) — usually [`Handler`](../reference/components/handler.md) or [`Computer`](../reference/components/computer.md) — which rests on the [consumer/provider trait duality](consumer-and-provider-traits.md). Fragment interpreters are providers written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) that draw their needs from the context as [impl-side dependencies](impl-side-dependencies.md), and a fragment parameterized by an abstract type uses [abstract types](abstract-types.md) to stay decoupled from concrete ones. The abstract syntax is built from type-level data — [`Product!`](../reference/macros/product.md) lists and [`Symbol!`](../reference/macros/symbol.md) strings — and dispatched on with [`UseDelegate`](../reference/providers/use_delegate.md) per [dispatching](dispatching.md). Composition uses the [handler combinators](../reference/providers/handler_combinators.md), and the language is bundled and extended through [namespaces](namespaces.md) defined with [`cgp_namespace!`](../reference/macros/cgp_namespace.md) and joined with [`delegate_components!`](../reference/macros/delegate_components.md). The [shell-scripting DSL example](../examples/shell-scripting-dsl.md) is a complete worked instance of the whole pattern. diff --git a/docs/errors/AGENTS.md b/docs/errors/AGENTS.md deleted file mode 100644 index 07b6ceb0..00000000 --- a/docs/errors/AGENTS.md +++ /dev/null @@ -1,58 +0,0 @@ -# AGENTS.md — the CGP error catalog - -This directory catalogs the compiler errors CGP produces *after* codegen, organized by the kind of error. Read [README.md](README.md) for what the catalog is and the hidden-versus-surfaced axis it is built around. The rules below govern how to keep these documents correct and useful, and they assume you have read the knowledge-base-wide rules in [../AGENTS.md](../AGENTS.md); this file adds the rules specific to the error catalog. - -Invoke the `/cgp` skill before writing or revising any document here, and write in the dual-reader prose style (the `/dual-reader-prose` skill). Each document is read both by an agent scanning for one error class and by a tool author reading a class start to finish, so every section opens with a self-contained topic sentence and frames any list with a sentence before and after. - -## What these documents are for - -An error document records the *anatomy* of one class of post-codegen compile failure, so that a tool author, a debugging agent, or an error-extraction sub-agent can recognize the class and act on it without re-deriving it from a raw error dump. Where the [debugging guide](../guides/debugging.md) is prescriptive — "trace it like this" — an error document is descriptive: it states what the mistake is, what the compiler prints, whether the root cause is in that output, and where. It is the reference the guide and the tooling index into. - -The catalog is the **canonical documentation for the post-codegen compile-fail cases**, which are pinned as UI fixtures in [cargo-cgp](https://github.com/contextgeneric/cargo-cgp) — CGP's first-class error toolchain. Those fixtures were migrated out of the former `cgp-compile-fail-tests` suite (now removed): a `cargo-cgp` UI fixture snapshots *both* the raw compiler output (`.rust.stderr`) and the readable form the tool rewrites it into (`.cgp.stderr`), so it captures the two halves this catalog describes. A macro's [implementation document](../implementation/README.md) still owns the failures the macro raises by *rejecting* its input (the `assert_macro_rejects` cases in `cgp-macro-tests`), but a failure that lands on the *compiler* checking the macro's output is documented here, and its `cargo-cgp` fixture links back here. See [What belongs here](README.md#what-belongs-here-and-what-does-not) for the dividing line. - -## The synchronization rule applies here too - -An error document must stay in sync with the code and its fixtures, and keeping it in sync is part of the change. The source of truth is a `cargo-cgp` UI fixture, which pins both halves the document describes: the raw compiler diagnostic in its `.rust.stderr` and the reshaped `cargo-cgp` output in its `.cgp.stderr`. When a change alters either — a new error code, a root cause that moves from hidden to surfaced, a caret that lands elsewhere, or a `[CGP-Exxx]` reshaping that changes — revise the matching document in the same change; when you add, move, or reclassify a fixture in `cargo-cgp`, update the document that catalogs its class so the catalog stays the canonical index. This is the cross-project [sync rule](../../AGENTS.md#the-two-projects-cgp-and-cargo-cgp) in action: the fixture lives in `cargo-cgp`, the class doc here, and the two move together when `../cargo-cgp` is checked out. Verify a claim against a real compilation, not memory: read the fixture's blessed `.rust.stderr`/`.cgp.stderr`, or re-run `cargo cgp check` on the fixture, before you describe what it prints. - -Document the present, not the history, following [../AGENTS.md](../AGENTS.md): describe the diagnostic as it is emitted today, and delete superseded wording outright rather than leaving "previously" traces. - -## Never record verbatim error output - -The defining rule of this catalog is that **a document summarizes the kind of error a class produces; it never pastes the verbatim output.** CGP errors are long — a single mistake in a deep dependency graph can print one failure per transitively dependent provider — and a pasted cascade would bloat the document and rot the instant a diagnostic shifts. Reproduce only the short *fragment* that carries a point (the one line naming the failing trait, or the shape of the near-contradiction between an error and its `help:` note), the way an implementation document quotes a slice of an expansion rather than the whole file. - -In place of the output, every document must record three facts, because these are what a tool and a debugging agent actually need: - -- **The kind of diagnostic** — the error code or codes, the trait or traits named (`IsProviderFor`, `DelegateComponent`, `CanUseComponent`, a consumer trait), and the recognizable shape (a lone `E0599` "method exists but bounds unsatisfied", an `E0277` note chain, an error immediately contradicted by a `help:` note, a repeated per-provider cascade). -- **Whether the root cause is present** — surfaced somewhere in the output, or hidden by the compiler's heuristics and absent entirely. This is the field that decides which subdirectory the class lives in and whether a reader should even look for a cause. -- **Where the root cause sits when present** — which note carries it, and its position in the output. Record when it is near the last or second-to-last message of a cascade, when it hides behind an elided `...` whose full form is written to the `long-type-….txt` file the compiler names, and any reliable landmark that lets a reader or tool jump to it. - -## Document structure - -Each error document follows the same shape so a reader can navigate any of them by habit. Open with a level-one heading naming the class and a one-sentence summary of the mistake and the error code it produces. Then proceed through these sections, using the same headings: - -- **What triggers it** — the CGP mistake that produces the class, with a short, self-contained code snippet of the failing input. Name the construct involved and link to its reference. -- **The raw diagnostic** — the *kind* of error **plain `rustc`/`cargo check` emits**, per the rule above: the code(s), the trait(s) named, and the shape. State plainly whether this is a hidden or a surfaced class. This section is the **fallback for when `cargo-cgp` is not available**, so it is kept in full even though the tool improves on it — an agent reading a raw cascade must still be able to recognize the class here. -- **Where the root cause is** — whether the true cause appears in the *raw* output and, if so, its position; if hidden, say so and explain briefly *why* the compiler suppresses it (the blanket-impl-plus-candidates heuristic, an elided type, cascade depth). -- **How cargo-cgp presents it** — how `cargo-cgp` reshapes the class into a readable form: the `[CGP-Exxx]` code(s) it stamps on the rewritten message (linking the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md)), and the shape of the result — a `root cause:` note over a dependency tree, a de-duplicated headline, or a pass-through when the tool does not (yet) rewrite the class. This is the section that tells a reader what they will actually see with the recommended toolchain; keep it distinct from the raw fallback above. -- **Resolving it** — the fix for the underlying mistake, and — for a hidden class — how to *promote* the error into a surfaced one (typically a `check_components!` at the wiring site) so the cause becomes visible. -- **Notes for tooling** — anything a `cargo-cgp`-style post-processor still needs beyond what it already does: a class it does not yet rewrite (a usability gap), a fragment recovering the cause needs compiler-internal introspection (`rustc_driver`) for, and the like. Omit the heading when the "How cargo-cgp presents it" section already covers the handling. -- **Backing fixtures** — a bullet list of the `cargo-cgp` UI fixtures that pin this class, each written as a **GitHub URL** (`https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/.rs`) with a one-line note of what it exercises. This is where the catalog's test pointers live; every documented class must have at least one fixture. (When `../cargo-cgp` is checked out, read the fixture there; the committed link stays a GitHub URL per the [cross-project rule](../../AGENTS.md#the-two-projects-cgp-and-cargo-cgp).) -- **Related** — links to the relevant reference documents, the [debugging guide](../guides/debugging.md) section that handles the class, the sibling error classes it contrasts with, and — for a problematic/defect class — the owning macro's `## Known issues`. - -## The error-code reference - -The [error_codes/](error_codes/README.md) directory is a supporting reference, not a set of classes: one entry per `rustc` error code the catalog surfaces, stating what the code means in plain Rust, the rule behind it, and the RFC or issue that defines it. It is the forward index (code → meaning → the classes that emit it) that complements the class documents (mistake → diagnostic). Three rules keep it honest. First, **every fact in an entry is verified against the official Rust documentation** — the [error index](https://doc.rust-lang.org/error_codes/), the [reference](https://doc.rust-lang.org/reference/), and the linked RFCs and `rust-lang/rust` issues — not against memory; the message wording is confirmed against a real compilation (a fixture's `.stderr`) where the class docs already pin it. Second, **a class document cites the local `error_codes/` entry in place of a raw `doc.rust-lang.org/error_codes/…` URL**, so the official links live in one place; the entry's "Where CGP produces it" section links back to every class that emits the code, and the two directions must stay in sync when a class is added, moved, or renamed. Third, an entry states the *Rust* rule only — the CGP-specific anatomy stays in the class document, so the two never duplicate each other. Add an entry when a class first surfaces a code not already covered. - -The directory also documents **`cargo-cgp`'s own `[CGP-Exxx]` codes** in [cargo-cgp-codes.md](error_codes/cargo-cgp-codes.md): a short reference that lists the codes the tool stamps on a rewritten message and points at `cargo-cgp`'s authoritative [error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md) for the full definitions. A class document's *How cargo-cgp presents it* section names the specific `[CGP-Exxx]` and links there, the same way it cites a local `rustc`-code entry — and since `cargo-cgp` owns those codes, the entry here is a pointer, not a second definition (the [sync rule](../../AGENTS.md#the-two-projects-cgp-and-cargo-cgp) keeps it from drifting). - -## Backing every class with a fixture - -Every error class must be backed by at least one `cargo-cgp` UI fixture, and reciprocally every post-codegen compile-fail fixture in `cargo-cgp` must be cataloged by a class here. When you write a class that has no fixture yet, add one in `cargo-cgp` following its [UI-test guide](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md): drop a `.rs` program (with `fn main`, and a `//@aux-build:` directive if it is cross-crate) into the category matching the output quality, bless it so both snapshots are written, and open it with a header stating what it exercises and linking the class document here as a GitHub URL. Then link it back from this class's Backing fixtures section. When `../cargo-cgp` is checked out, do both in the same change per the [sync rule](../../AGENTS.md#the-two-projects-cgp-and-cargo-cgp); when it is not, state the fixture that needs adding. - -## Handling the acceptable / problematic / rejection split - -Two orthogonal classifications apply, and keeping them separate is what stops the catalog from duplicating or losing information. **Where the fixture lives in `cargo-cgp`** is decided by *output quality* — `ok/` (clean compile), `acceptable/` (the tool leads with the cause), or `usability/` (the cause is present but buried) — per `cargo-cgp`'s own guide; this catalog does not mirror those directories. **Whether the failure is a CGP defect** is the distinction that still governs this catalog: an **acceptable** failure — one CGP intentionally defers to the compiler — is documented here in full, and nothing about it belongs in a macro's Known issues, because it is not a defect; a **problematic** failure — a CGP defect — is cataloged here for the *observable error a user hits*, but the explanation of why it is a defect stays under the owning macro's `## Known issues`, linked both ways and updated together when the defect is fixed. A failure the macro catches by **rejecting its input** is not cataloged here at all — it stays with the macro, per [What belongs here](README.md#what-belongs-here-and-what-does-not). - -## Gathering an error with a sub-agent - -Writing or verifying a document here means reading real compiler output, and that output is often long enough to waste the context of the agent doing the writing. When you are confirming a single fact about a class you already know — the error code a fixture emits, whether a `downstream crates may implement` note appears, which `Symbol` a leaf names — grep the captured `.stderr` for that line first (`grep -nE '^error'` to classify, then the class's [signature pattern](../guides/debugging.md#grep-for-the-suspected-line-instead-of-reading-the-whole-log)), which is cheaper than a sub-agent. Delegate the reading when the grep will not settle it — a new class you have not characterized, or output tangled across several classes at once. The [error-extraction sub-skill](../skills/cgp/references/error-extraction.md) defines how a sub-agent compiles a fixture or a scratch reproduction, captures the diagnostic, and returns only the compact anatomy this catalog records — the class, whether the root cause is present, and its position — rather than the raw dump. Spawn a sub-agent with that skill to gather the facts for a class, then write the document from its summary. The same delegation applies in an ordinary debugging session: when a CGP error is too long to read inline, hand it to a sub-agent and act on the summary. Keeping the extraction technique in the skill and the extracted facts in the catalog means the two describe the same anatomy from two directions. diff --git a/docs/errors/README.md b/docs/errors/README.md deleted file mode 100644 index 87143b81..00000000 --- a/docs/errors/README.md +++ /dev/null @@ -1,88 +0,0 @@ -# CGP Error Catalog - -This directory is a catalog of the compiler errors a programmer encounters when using CGP, organized by the *kind* of error rather than by the macro that produced it. Each document describes one class of failure: the mistake that triggers it, the shape of the diagnostic the Rust compiler prints, whether that diagnostic contains the real root cause, and — when it does — where in the output to find it. The catalog is one of the knowledge base's top-level sections, alongside [reference/](../reference/README.md), [concepts/](../concepts/README.md), [examples/](../examples/README.md), [guides/](../guides/README.md), and [related-work/](../related-work/README.md). - -## Why this exists - -A CGP macro expands to ordinary Rust, so many mistakes are not caught by the macro at all — they surface later, when the compiler type-checks the generated code. Those downstream errors are the subject of this catalog. They are shaped by CGP's machinery in ways that make them hard to read: a single mistake can produce a wall of errors naming generated types the user never wrote, and the root cause is often buried or, worse, suppressed entirely. Documenting these classes in one place serves three readers at once. - -The first reader is a **tool author**. That tool now exists: [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) is CGP's first-class toolchain, a cargo subcommand that post-processes `rustc`'s output into a compact, root-cause-first form, much as Clippy layers its own analysis on top of the compiler. It needs a complete map of which error classes CGP produces, which of them hide the root cause, and where the root cause sits when it is present, to decide what to extract, what to suppress, and how to re-present it; where it reaches into compiler internals through `rustc_driver` to recover *suppressed* information, it needs to know, class by class, which information the compiler hides. This catalog is that map — and each class here records both the raw diagnostic and how `cargo-cgp` reshapes it, so the catalog and the tool stay two views of one truth. - -The second reader is an **agent debugging CGP code**, in this repository or any project that uses CGP. Recognizing an error's class on sight — before decoding a single nested type — tells the agent what kind of mistake to look for and which technique will surface it. Grepping the error headlines (`grep -nE '^error'`) is the mechanical form of that recognition, and each class here has a signature line an agent can grep to confirm its cause without reading the whole cascade — the [debugging guide](../guides/debugging.md) collects those patterns in a table. The guide is the playbook; this catalog is the reference the playbook indexes into. - -The third reader is a **sub-agent extracting an error message on the main agent's behalf**. CGP error output is frequently long enough that reading it wastes a main agent's context window, so the [error-extraction sub-skill](https://github.com/contextgeneric/cgp/blob/main/docs/skills/cgp/references/error-extraction.md) delegates the reading to a sub-agent that returns a compact summary. That summary is only useful if it reports the same facts every document here records — the class, whether the root cause is present, and its position — so the catalog and the sub-skill describe the same anatomy from two directions. - -## What belongs here, and what does not - -This catalog covers only **compilation failures that occur after CGP codegen** — input a macro *accepts* and lowers to Rust that then fails to type-, trait-, or borrow-check. This is exactly the set of failures pinned by the UI fixtures in [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md) (migrated there out of the former `cgp-compile-fail-tests` suite, now removed), and the catalog is the canonical documentation for them: each documented class is backed by a `cargo-cgp` fixture, and the fixtures cross-link back to the class they exercise. Because a `cargo-cgp` fixture snapshots the raw compiler output *and* the tool's reshaped output side by side, each class here can describe both — the raw diagnostic as a fallback, and how `cargo-cgp` presents it. - -Errors a macro raises by **rejecting its input** do not belong here. When a macro refuses malformed or unsupported input and returns a spanned `syn::Error` during expansion, that diagnostic is a property of the macro's parser, and it stays in the macro's [implementation document](../implementation/README.md) — described there and pinned by an `assert_macro_rejects` case in `cgp-macro-tests`. The dividing line is simple: if the macro produced the error, it is documented with the macro; if the *compiler* produced the error while checking the macro's output, it is documented here. - -One nuance follows the [acceptable / problematic split](AGENTS.md#handling-the-acceptable--problematic--rejection-split) — whether a failure is a CGP *defect*. An **acceptable** failure is one CGP intentionally defers to the compiler because it lacks the whole-program view the check needs; its documentation lives here in full. A **problematic** failure is a CGP *defect* — input a macro should have rejected, or an expansion that emits invalid Rust; the observable error a user hits is cataloged here like any other class, but the explanation of *why it is a defect and what the fix would be* stays under the owning macro's `## Known issues`, cross-linked both ways. (This is orthogonal to which `cargo-cgp` directory — `acceptable/`, `usability/`, `ok/` — the backing fixture lives in, which is decided by output quality.) - -## The central axis: hidden versus surfaced - -The single most important distinction in this catalog is whether the compiler **surfaces** or **hides** the root cause of an unsatisfied dependency, because the two produce completely different diagnostics from the very same mistake. CGP wiring is resolved lazily, so a provider whose impl-side dependency the context cannot meet is wired without complaint and only fails when the wiring is exercised. *How* it is exercised decides what the user sees. - -When the failure is forced through a **check trait** — `check_components!` asserting `CanUseComponent`, which walks through [`IsProviderFor`](../reference/traits/is_provider_for.md) — the compiler evaluates the provider's `where` clause and reports the real unmet bound, naming the missing `HasField`, abstract type, or transitive dependency. The root cause is **surfaced** — the concrete missing bound is named in the diagnostic (for a single-component check, in the compiler's `help:` note), and a `required for …` note chain traces the dependency path from it back to the check. These errors are cataloged under [checks/](checks/). - -When the same broken wiring is instead exercised by **calling the consumer-trait method directly** on the context, the compiler sees a blanket impl of the consumer trait alongside the other candidate impls, cannot commit to one, and falls back to a heuristic that reports only "the method exists but its trait bounds were not satisfied" — naming the consumer and provider traits but *not* descending into the dependency that actually failed. The root cause is **hidden**: it is absent from the output, not merely buried. Because these errors report nothing about the true cause, mixing them with the surfaced classes would mislead a reader into looking for a root cause that is not there. They are isolated under [hidden/](hidden/), and recovering their root cause requires either re-checking the wiring to promote the error into a surfaced one, or the compiler-internal introspection `cargo-cgp` performs through `rustc_driver` (which is how, under its next-generation solver, it already surfaces causes plain `cargo check` hides). - -## The verbosity problem: many errors, one cause - -A second recurring difficulty is volume: a single mistake deep in a dependency graph surfaces at *every* provider that transitively needs it, so the compiler prints one error per affected provider even though there is only one thing to fix. If a `FooProvider` needs a `foo` field, a `BarProvider` depends on `Foo`, and a `BazProvider` depends on `Bar`, then a missing `foo` field produces three separate failures naming `FooProvider`, `BarProvider`, and `BazProvider`. The count of errors reflects the depth of the graph, not the number of mistakes. - -Because of this, **the documents here never record verbatim error output.** Reproducing a multi-screen cascade would bloat the catalog and rot the moment a diagnostic shifts. Instead each document summarizes the *kind* of message a class produces, states whether the root cause appears anywhere in it, and pins the *position* where it appears when it does — very often near the last or second-to-last error in a cascade, since the innermost failing bound is reported after the outer ones. Recording the position is what lets a tool, or an agent, jump to the cause instead of reading the noise. The [authoring conventions](AGENTS.md) make this rule precise. - -## Organization - -The catalog is divided into four subdirectories, so a reader lands in the right class before decoding any type. The first three follow the hidden-versus-surfaced axis above; the fourth holds a failure that arises earlier, in the macro's lowering itself. Each document is registered in the catalog below in the same change that adds it. - -The [hidden/](hidden/) directory holds the classes where the compiler **suppresses** the root cause — the errors a user meets by exercising broken wiring through a consumer trait rather than a check. These are isolated precisely because their diagnostics report nothing about the true cause, so a reader must know not to look for one. - -The [checks/](checks/) directory holds the classes where a check trait **surfaces** the root cause through `IsProviderFor`, and the classes that are dominated by the *volume* of a surfaced cascade rather than by any single message. - -The [wiring/](wiring/) directory holds the whole-program **structural** failures — coherence conflicts, orphan-rule violations, wiring cycles, unconstrained generics, and the namespace-specific coherence collisions — where the compiler reports a definite error code (`E0119`, `E0210`, `E0275`, `E0207`) and the difficulty is mapping that code back to the wiring mistake rather than a hidden or cascading cause. Because the catalog groups by usage as well as by error code, the namespace collisions live in their own documents even where they share a code (`E0119`) with a plain duplicate declaration. - -The [lowering/](lowering/) directory holds the classes where the failure is not in the wiring at all but in what a macro *lowered* the user's input into — an accepted shorthand or type combination expanded into Rust that is ill-formed on its own terms (an unsized type, for instance), so the compiler rejects the generated code rather than any wiring decision. - -A fifth directory, [error_codes/](error_codes/), is a supporting *reference* rather than a class of error: one entry per `rustc` error code the catalog surfaces (`E0119`, `E0117`, `E0207`, `E0210`, `E0275`, `E0277`, `E0428`, `E0576`, `E0599`), stating what the code means in plain Rust, the rule behind it, and the RFC or issue that defines it, grounded in the official documentation. The class documents cite these entries in place of the raw `doc.rust-lang.org` URLs, so the Rust-language facts live in one verified place and the class docs carry only the CGP-specific anatomy. The same directory holds [cargo-cgp-codes.md](error_codes/cargo-cgp-codes.md), the pointer to `cargo-cgp`'s own `[CGP-Exxx]` codes that each class's *How cargo-cgp presents it* section cites. - -## Catalog - -This section indexes every error document, grouped by the subdirectory it lives in. - -Hidden-cause errors — [hidden/](hidden/): - -- [Unsatisfied dependency (hidden)](hidden/unsatisfied-dependency.md) — a provider's impl-side dependency is unmet, and the failure is triggered by a direct consumer-trait method call, so the compiler reports only that the method's bounds are unsatisfied (`E0599`/`E0277`) and hides the missing dependency. - -Surfaced and cascading errors — [checks/](checks/): - -- [Check-trait failure (surfaced)](checks/check-trait-failure.md) — the same unmet dependency forced through `check_components!`, where `IsProviderFor` surfaces the concrete missing bound (a `HasField` or CGP capability) at the wiring site. -- [Unsatisfied ordinary trait bound (surfaced)](checks/ordinary-trait-bound.md) — an impl-side dependency that is an *ordinary* Rust trait (`Eq`, `Clone`, …) on an abstract type or impl generic, unmet by the concrete type the context supplies; a check surfaces the ordinary bound (`f64: Eq`) as the primary `E0277`. -- [Verbose dependency cascade](checks/verbose-cascade.md) — one deep mistake reported at every transitively dependent provider, and how to locate the single root cause among the repeats. -- [Higher-order provider layer failure (surfaced)](checks/higher-order-provider-layer.md) — a checked higher-order provider with an unmet dependency, where the diagnostic's shape (chain depth, which `where` clause the caret sits on) identifies whether the inner or the outer layer is at fault. -- [Unregistered namespace path](checks/unregistered-namespace-path.md) — a component routed through a joined namespace to a path that no entry ever binds, so the *lookup* finds no delegate; a check surfaces it as an `E0277` on the path-keyed `DefaultNamespace`/`DelegateComponent` bound. - -Structural wiring errors — [wiring/](wiring/): - -- [Conflicting wiring](wiring/conflicting-wiring.md) — the same key or name wired or declared twice, producing coherence (`E0119`) or duplicate-definition (`E0428`) errors. -- [Overlapping namespace forwarding](wiring/namespace-forwarding-conflict.md) — two blanket forwarding impls that each cover every key (joining two namespaces, or a namespace join plus a bare-key `for` loop), a fully-generic `E0119` with no downstream note. -- [Namespace override conflict](wiring/namespace-override-conflict.md) — a specific entry that overrides a key a namespace already claims (a context re-wiring a registered path, or a child namespace redefining an inherited entry), an `E0119` on a concrete key. -- [Orphan-rule violation](wiring/orphan-rule.md) — a generated impl registering into a foreign namespace with no local type (`E0210`, or `E0117`), as when a `#[default_impl]` or a `cgp_namespace!` re-open targets a namespace and key the crate does not own. -- [Wiring cycle](wiring/wiring-cycle.md) — a delegation that chases its own tail: an `E0275` overflow when forced through a check, but the hidden `E0599` when reached by a plain method call. -- [Namespace inheritance cycle](wiring/namespace-inheritance-cycle.md) — namespaces whose parent chain loops (`A: B`, `B: A`, or `A: A`), an `E0275` overflow caught *eagerly at the `cgp_namespace!` definitions* rather than lazily at a use site. -- [Unconstrained generic](wiring/unconstrained-generic.md) — a per-entry generic that never reaches the key, leaving an impl parameter unconstrained (`E0207`). - -Lowering errors — [lowering/](lowering/): - -- [Ill-formed generated type](lowering/ill-formed-generated-type.md) — a macro lowers an unsupported field- or argument-type shorthand (such as `Option<&[T]>`) into a generated bound naming an ill-formed, unsized type, which the compiler rejects with the `E0277` `Sized` form. -- [Unresolved imported abstract type](lowering/unresolved-imported-type.md) — a `#[use_type]` import names an associated type the owning trait does not declare, so the rewritten `::WrongName` path resolves to nothing and the compiler rejects it with `E0576`, its caret on the name the user wrote. - -Error-code reference — [error_codes/](error_codes/): - -- One entry per `rustc` code the catalog surfaces — [`E0119`](error_codes/e0119.md), [`E0117`](error_codes/e0117.md), [`E0207`](error_codes/e0207.md), [`E0210`](error_codes/e0210.md), [`E0275`](error_codes/e0275.md), [`E0277`](error_codes/e0277.md), [`E0428`](error_codes/e0428.md), [`E0576`](error_codes/e0576.md), [`E0599`](error_codes/e0599.md) — each recording what the code means in plain Rust, the rule behind it, and where CGP produces it. Plus [cargo-cgp-codes.md](error_codes/cargo-cgp-codes.md), the pointer to `cargo-cgp`'s `[CGP-Exxx]` codes. See the [error-code reference index](error_codes/README.md). - -## Relationship to the rest of the knowledge base - -This catalog is one of the views of CGP's truth and is bound by the [synchronization rule](../AGENTS.md): a class whose diagnostic the code no longer produces is a bug in the change that made it stale. It leans on the other sections rather than restating them. It links to the [reference](../reference/README.md) for the traits and macros a class involves, to the [check-traits concept](../concepts/check-traits.md) for why wiring is lazy, and above all to the [debugging guide](../guides/debugging.md), which is the prescriptive playbook this reference-style catalog supports. The rules for authoring and maintaining these documents — including how they stay in sync with the `cargo-cgp` fixtures and how a sub-agent extracts the errors they describe — live in [AGENTS.md](AGENTS.md). diff --git a/docs/errors/checks/check-trait-failure.md b/docs/errors/checks/check-trait-failure.md deleted file mode 100644 index 9137636c..00000000 --- a/docs/errors/checks/check-trait-failure.md +++ /dev/null @@ -1,103 +0,0 @@ -# Check-trait failure (surfaced) - -A check forces an unsatisfied impl-side dependency through `IsProviderFor`, so the compiler names the real missing bound (`E0277`) at the wiring site — the surfaced counterpart of the [hidden unsatisfied dependency](../hidden/unsatisfied-dependency.md), produced from the very same mistake. - -## What triggers it - -This class arises from exactly the mistake behind the [hidden class](../hidden/unsatisfied-dependency.md) — a provider wired onto a context that cannot meet the provider's impl-side dependency — but exercised through a [`check_components!`](../../reference/macros/check_components.md) assertion (or the fused `delegate_and_check_components!`) instead of a direct method call. The check is what changes the outcome: it asserts `CanUseComponent` for each listed component, and that assertion requires `IsProviderFor` as a *direct* bound, which forces the compiler to evaluate the provider's `where` clause rather than suppressing it. - -```rust -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasName, // impl-side dependency -{ - fn greet(&self) { - let _ = self.name(); - } -} - -#[derive(HasField)] -pub struct Person { - pub age: u8, // no `name` field -} - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} - -// Forces the failure here, at the wiring, instead of at a later call site. -check_components! { - Person { - GreeterComponent, - } -} -``` - -A `check_components!` is the canonical way to force this diagnostic, but it is not the only one: any *direct* obligation on a capability bound produces the same shape. A [`#[use_type]`](../../reference/attributes/use_type.md) foreign import (`HasScalarType.Scalar in Types`) puts the capability bound `Types: HasScalarType` — grounded to `::Types: HasScalarType` for a nested import — onto the *generated trait itself*, so asserting or using that trait for a context whose supplied type does not implement the capability surfaces the identical `E0277` with a capability leaf, without any `check_components!`. Here the `required for …` chain runs through the trait's own `where` bound (`required by a bound in CanCalculateArea` / `GetScalar`) rather than through `CanUseComponent`, but the leaf, the `DelegateComponent`/`IsProviderFor` `help:`, and the position of the cause are the same. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class: the compiler prints an `E0277` that names the concrete missing bound, unlike the hidden class that omits it. The primary error reports that `Person: CanUseComponent` is not satisfied, and its caret lands on the `GreeterComponent` entry *inside the `check_components!` block* — not on the `Person` context type — because the check re-spans the shared context token onto each listed component in turn. Immediately below, a `help:` note gives the actual unmet leaf bound: that `HasField` is not implemented for `Person`, "but trait `HasField` is implemented for it." That second half is a useful landmark — the compiler is pointing at the *nearest existing* field impl, which tells you the context has a field, just not the one the provider expects. - -Below the `help:` note, a `required for …` chain traces the dependency path outward from the leaf: `Person` to implement `HasName`, then `GreetHello` to implement `IsProviderFor`, then `Person` to implement `CanUseComponent`, and finally the bound in the generated `__CheckPerson` trait that the `check_components!` block emitted. The chain is the scaffolding; the leaf in the `help:` note is the cause. - -What makes the cause visible is that the check produces a *direct* trait obligation, and this is where the class differs mechanically from its hidden twin. A `check_components!` asserts `Person: CanUseComponent` as a bound on the generated `__CheckPerson` trait, so the solver must prove that bound outright — and proving it means discharging the whole `where`-clause chain down to the leaf and reporting the first bound that cannot be met. The `required for …` notes are simply `rustc`'s ordinary `E0277` obligation-tracing output for that proof. It is exactly the path the [hidden class](../hidden/unsatisfied-dependency.md) never takes: a method call lets the solver abandon an inapplicable blanket impl at the top instead of proving a direct bound, so it never descends to the leaf. [`IsProviderFor`](../../reference/traits/is_provider_for.md) is the supertrait that carries the provider's own `where` clause into this chain, which is why the leaf is named here and suppressed there. The `help:` note's "but trait `HasField` is implemented for it" is a second piece of standard machinery — `rustc`'s "a similar impl exists" hint, pointing at the nearest impl of the same trait to show the context has *a* field, just not the expected one. - -## Where the root cause is - -The root cause is **present**, and it is near the top — in the compiler's `help:` note, not at the end of the output. This is the opposite of what a reader might expect from a long note chain: the concrete unmet bound (`HasField`) is stated early, and the `required for …` notes that follow build *outward* from it toward the check trait, rather than drilling down to it. The caret's position is the other half of the value — it sits on the wiring entry the user controls, so the error points at the fix site rather than at a distant call. When many providers depend transitively on one leaf, the output multiplies into a [verbose cascade](verbose-cascade.md), and the position guidance shifts to *which block* carries the actionable cause; for a single checked component it is simply the `help:` note. - -## When the derive is missing entirely - -A distinct sub-case of this class is worth recognizing because its fix differs and its diagnostic drops the usual landmark: the mistake is not a missing *field* but a missing `#[derive(HasField)]` on the context altogether. When the struct has the field the getter names but no derive, it has *no* `HasField` impls at all, so the getter trait is still unsatisfiable and the check still fails with the same `CanUseComponent` / `IsProviderFor` / `HasField` shape. The tell is what is *absent*: the `help:` note names the missing `HasField` and points at the `struct` definition, but there is **no** "but trait `HasField<…>` is implemented for it" line, because the context implements the trait for no field at all. The near-impl hint that a single missing field always produces cannot appear when every field is missing. - -Read the absence as its own signal: a checked context that implements `HasField` for nothing behind a `#[derive(HasField)]`-shaped requirement has most likely forgotten the derive, and the fix is to add `#[derive(HasField)]` to the struct, not to add fields one at a time. A tool handling this class should special-case it — zero `HasField` impls plus an unmet `HasField` leaf means the derive, and the headline should say so rather than sending the user after a single field. - -## When the dependency is an equality-pinned abstract type - -A second sub-case is worth recognizing because its diagnostic is an `E0271` rather than an `E0277`, and because the wiring it points at is a *type* choice rather than a field or a provider. An impl-side dependency can pin an [abstract type](../../concepts/abstract-types.md) to a concrete one — `#[use_type(HasErrorType.{Error = AppError})]` on a provider, which emits `Self: HasErrorType` — while the context binds that same abstract type elsewhere, by wiring its component to `UseType` or by implementing the trait directly. When the two disagree, the *trait* half of the bound still holds (the context genuinely implements `HasErrorType`) and only the associated-type projection fails, so the compiler reports a type mismatch rather than an unimplemented trait. - -The tell is the headline shape: `` type mismatch resolving `::Error == AppError` ``, followed by an `expected this to be …` note whose caret lands on the `#[cgp_type]` attribute that *defined* the abstract type — nowhere near either side of the disagreement. Two things make this harder to read than the field cases above. The type the context actually supplies is nowhere in the message: rustc names only what was required, so a reader must find the wiring themselves to learn what it was required *against*. And because one abstract type is shared by everything in the context that touches it, a single wrong binding surfaces at every consumer that raises through it, multiplying into a [verbose cascade](verbose-cascade.md). The fix is to reconcile the two: bind the component to the type the provider requires, or relax the provider to accept the one the context supplies. - -## How cargo-cgp presents it - -`cargo-cgp` recognizes this class and leads with the cause. It keeps the `E0277` code but rewrites the headline to `[CGP-E001] the consumer trait \`CanGreet\` is not implemented for context \`Person\``, and replaces the `required for …` scaffolding with a single `root cause:` note over a compact dependency tree — `[CGP-E101]` consumer-trait-impl hop → `[CGP-E102]` provider-trait-impl hop → the leaf. The leaf is `[CGP-E106] missing field \`name\` on \`Person\`` for a genuinely absent field, or `[CGP-E108]` for the [derive-missing variant](#when-the-derive-is-missing-entirely), which coalesces every underived field on the struct into one "add `#[derive(HasField)]`" cause rather than listing them singly. The `#[use_type]` foreign-import form bottoms out on `[CGP-E107]` (the context supplies no wiring for the capability). The `Symbol!("name")` spine is resugared and the `CanUseComponent`/`IsProviderFor`/`__Check…` frames are dropped, so what remains is the field to fix and the wiring entry it hangs from. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -The [equality-pinned abstract type](#when-the-dependency-is-an-equality-pinned-abstract-type) is reshaped the same way into its own class, `[CGP-E017]`: `` expected the abstract type `Error` of `HasErrorType` on `MockApp` to be `AppError`, but found `String` ``, over a `[CGP-E112]` leaf. Both halves of the disagreement are named — the required type read off the failing projection, the supplied one by normalizing it, so a `UseType` wiring and a direct impl are read alike — and a `help` names the wiring entry to change (`` wire `ErrorTypeProviderComponent` to `UseType` ``), recovered from the trait rather than guessed. Because one abstract type is shared, the cascade this class produces is also coalesced: every consumer that fails through it is listed in one block over a single root-cause tree. - -## Resolving it - -The fix is what the diagnostic already points to: satisfy the named leaf bound. Add the `name` field to `Person`, or wire the getter component to the existing field, so `Person` implements `HasName` and `GreetHello` becomes a valid provider for the component. Because the check surfaced both the concrete bound *and* the wiring entry, no further tracing is usually needed — which is exactly why the standard remedy for a [hidden](../hidden/unsatisfied-dependency.md) failure is to add a check and read this class instead. - -## Notes for tooling - -This class is the *target* `cargo-cgp` normalizes toward, and it already handles it fully (above). It is also where the hidden class lands once promoted: synthesizing a `check_components!` for a [hidden](../hidden/unsatisfied-dependency.md) failure produces exactly this diagnostic, so both reduce to the same `[CGP-E001]` headline and `root cause:` tree. - -## Backing fixtures - -- [`acceptable/fields/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/missing_dependency.rs) — the surfaced case for `GreetHello`'s unmet `Self: HasName`; its `.rust.stderr` pins the raw `help:` note naming `HasField` and the caret on `GreeterComponent`, and its `.cgp.stderr` the `[CGP-E001]`/`[CGP-E106]` reshaping. Its use-site counterpart, [`acceptable/use-site/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/missing_dependency.rs), reaches the same mistake by a method call — the [hidden](../hidden/unsatisfied-dependency.md) `E0599` in the raw output, which `cargo-cgp`'s next-gen solver recovers to the same headline. -- [`acceptable/use-type/use_type_foreign_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-type/use_type_foreign_unsatisfied.rs) — the capability bound reached through a `#[use_type]` foreign import instead of a check: naming a component for a `Types` that does not implement the imported `HasScalarType` surfaces `E0277`, pinning that the foreign bound is *enforced on the generated trait* rather than silently dropped. -- [`acceptable/use-type/use_type_nested_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-type/use_type_nested_unsatisfied.rs) — the same through a *nested* two-hop import, so the grounded bound `::Types: HasScalarType` is the one enforced, confirming the transitively-grounded foreign bound is checked at depth. -- [`acceptable/types/abstract_type_mismatch.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/types/abstract_type_mismatch.rs) — the [equality-pinned abstract type](#when-the-dependency-is-an-equality-pinned-abstract-type): a context wiring `HasScalarType` to `UseType` under a provider that pins it to `f64`. Its `.rust.stderr` pins the raw `E0271` with its caret on the `#[cgp_type]` attribute and no mention of `u32`, and its `.cgp.stderr` the `[CGP-E017]`/`[CGP-E112]` reshaping with the `UseType` help. -- [`acceptable/fields/missing_has_field_derive.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/missing_has_field_derive.rs) — the [derive-missing variant](#when-the-derive-is-missing-entirely): a `Person` with the `name` field but no `#[derive(HasField)]`, so the raw output drops the "but trait `HasField<…>` is implemented" landmark (the signal the whole derive is missing), and `cargo-cgp` coalesces it to one `[CGP-E108]` cause. Related field-derive fixtures — [`base_area_2`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/base_area_2.rs), [`empty_field_struct`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/empty_field_struct.rs), [`underived_and_missing_field`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/underived_and_missing_field.rs) — pin the coalescing and the mixed missing-plus-underived case. - -## Related - -- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the hidden counterpart; the two are the two halves of one phenomenon, and promoting the hidden one yields this class. -- [Unsatisfied ordinary trait bound (surfaced)](ordinary-trait-bound.md) — the sibling surfaced class whose leaf is an ordinary Rust trait (`Eq`, `Clone`) on a concrete type rather than a CGP capability like `HasField`; the leaf's kind changes the `help:` note and the position of the cause. -- [Verbose dependency cascade](verbose-cascade.md) — this diagnostic multiplied when many providers depend transitively on one leaf. -- [`check_components!`](../../reference/macros/check_components.md), [`CanUseComponent`](../../reference/traits/can_use_component.md), and [`IsProviderFor`](../../reference/traits/is_provider_for.md) — the macro and traits this class is expressed through. -- [Debugging CGP compile errors](../../guides/debugging.md) and the [check-traits concept](../../concepts/check-traits.md) — why the check moves the error here and how to read it. diff --git a/docs/errors/checks/higher-order-provider-layer.md b/docs/errors/checks/higher-order-provider-layer.md deleted file mode 100644 index c8705dc7..00000000 --- a/docs/errors/checks/higher-order-provider-layer.md +++ /dev/null @@ -1,75 +0,0 @@ -# Higher-order provider layer failure (surfaced) - -A checked higher-order provider whose dependency is unmet surfaces the real leaf bound (`E0277`) like any [check-trait failure](check-trait-failure.md), but the diagnostic's shape also tells you *which layer* of the provider stack failed — and reading that shape is the difference between fixing the wrapper and fixing what it wraps. - -## What triggers it - -This class arises when a [higher-order provider](../../concepts/higher-order-providers.md) — a provider parameterized by another provider — is wired onto a context that cannot meet a dependency in one of its layers, and the wiring is checked. The outer provider carries its own impl-side dependency, and the inner provider it wraps carries a different one; the context may satisfy either, both, or neither. When exactly one layer's dependency is unmet, the check still fails, and the question a reader must answer is which layer to fix. - -```rust -#[cgp_impl(new BaseArea)] -impl AreaCalculator -where - Self: HasBaseArea, // the INNER layer's dependency -{ /* … */ } - -#[cgp_impl(new ScaledArea)] -#[use_provider(Inner: AreaCalculator)] -impl AreaCalculator -where - Self: HasScaleFactor, // the OUTER layer's dependency -{ /* … */ } - -#[derive(HasField)] -pub struct Rectangle { - pub scale_factor: f64, // present: the outer layer is satisfied - // no `base_area`: the inner layer is not -} - -delegate_components! { - Rectangle { AreaCalculatorComponent: ScaledArea } -} - -check_components! { - Rectangle { AreaCalculatorComponent } -} -``` - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class: the `help:` note names the concrete unmet leaf exactly as in a single-layer [check-trait failure](check-trait-failure.md), and the leaf tells you the offending field. What is specific to a higher-order provider is that the *rest* of the diagnostic differs by layer, so the two cases are told apart by shape rather than by the leaf alone. - -When the **inner** layer fails, the compiler prints **two** `E0277` blocks. The first is an intermediate block reporting that the inner provider does not implement its provider trait for the concrete context — `BaseArea: AreaCalculator` is not satisfied — carrying the [near-contradiction](../../guides/debugging.md) hint that `AreaCalculator<__Context__>` *is* implemented for `BaseArea` (the generic impl exists but is inapplicable here). The second block is the real one: `Rectangle: CanUseComponent` is not satisfied, its `help:` naming the missing `HasField`, its `unsatisfied trait bound introduced here` caret landing on the *inner* provider's `Self: HasBaseArea` clause, and its `required for …` chain running through `BaseArea`'s `IsProviderFor` and then — after a `1 redundant requirement hidden` note — through `ScaledArea`'s `IsProviderFor` to `CanUseComponent`. The chain passing through both providers' `IsProviderFor`, and the extra intermediate block, are the marks of an inner-layer failure. - -When the **outer** layer fails, the compiler prints a **single**, shorter `E0277` block. It is the `Rectangle: CanUseComponent` block, its `help:` naming the missing `HasField`, its caret landing on the *outer* provider's `Self: HasScaleFactor` clause, and its chain running from `ScaledArea`'s `IsProviderFor` straight to `CanUseComponent` — with no `redundant requirement` note and no mention of the inner `BaseArea` at all. The outer layer fails before it ever delegates inward, so the inner provider never enters the proof. - -## Where the root cause is - -The root cause is **present** in the `help:` note of the `CanUseComponent` block in both cases, and the failing *layer* is present too, in two reinforcing places. The `unsatisfied trait bound introduced here` caret sits on the `where` clause of the provider whose dependency is unmet — the inner provider for an inner failure, the outer provider for an outer one — which is the most direct signal. The chain depth confirms it: an inner failure runs through *both* providers' `IsProviderFor` and carries the `1 redundant requirement hidden` note, while an outer failure stops at the outer provider's `IsProviderFor` and never names the inner one. The intermediate `Inner: ProviderTrait` block that appears only for the inner case is a third, weaker signal — its presence points inward, but it is also the noise a reader should skip past to reach the `CanUseComponent` block that carries the leaf. - -## How cargo-cgp presents it - -`cargo-cgp` reshapes both cases into a single `[CGP-E001]` headline — `[CGP-E001] the consumer trait \`CanCalculateArea\` is not implemented for context \`Rectangle\`` — over one `root cause:` tree, and **encodes the failing layer in the shape of that tree** rather than leaving it to be read from chain depth and caret position. For the inner failure the tree bottoms out at `[CGP-E106] missing field \`base_area\`` beneath *two* stacked `[CGP-E102]` provider-trait hops — first `ScaledArea`, then the `BaseArea` it wraps — before reaching `[CGP-E105] HasBaseArea` and the field; the two provider hops are the wrapper relationship made explicit. For the outer failure the tree bottoms out at `[CGP-E106] missing field \`scale_factor\`` beneath a *single* `[CGP-E102]` hop (`ScaledArea`) with the inner provider never named, exactly mirroring the shorter raw block. The intermediate near-contradiction block that clutters the raw inner-layer output is dropped entirely, so the recovered form reads the same in both cases save for that one-hop-versus-two-hop depth. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -Fix the dependency at the layer the diagnostic points to. For the inner failure, supply the field the inner provider needs — add `base_area` to `Rectangle` so `BaseArea` implements `HasBaseArea`; for the outer failure, supply the field the outer provider needs — add `scale_factor` so `ScaledArea` implements `HasScaleFactor`. Wiring is unaffected: the stack `ScaledArea` is correct in both cases, and the fix is always to satisfy the named leaf, never to change the delegation. - -When a stack is deep enough that reading the chain is awkward, force the layer boundary explicitly with the `#[check_providers(...)]` form of [`check_components!`](../../reference/macros/check_components.md). It changes the assertion from `CanUseComponent` on the context to `IsProviderFor` on each named provider, so a dependency missing only from the outer wrapper errors on that provider's line alone while one missing from the inner provider errors on both — pinpointing the layer without decoding the chain. The [debugging guide](../../guides/debugging.md) prescribes this move; this document is the anatomy behind it. - -## Notes for tooling - -`cargo-cgp` already does the work this class used to ask of a post-processor — it suppresses the intermediate near-contradiction block and reconstructs the wrapper relationship as the stacked `[CGP-E102]` hops of the tree. What it does *not* yet do is state the layer in words: the tree shows the wrapper structurally (two hops for an inner failure, one for an outer), but a reader still infers "inner versus outer" from the depth rather than reading a phrase like "needed by the inner provider `BaseArea`, wrapped by `ScaledArea`." A tool wanting to name the layer explicitly can emulate `#[check_providers(...)]`, re-asserting `IsProviderFor` per layer to localize it mechanically. - -## Backing fixtures - -- [`acceptable/providers/higher_order_inner_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/providers/higher_order_inner_dependency.rs) — `ScaledArea` wired onto a `Rectangle` with `scale_factor` but no `base_area`, so the inner layer fails; its `.rust.stderr` pins the two-block shape, the `HasField` leaf, the caret on `BaseArea`'s `Self: HasBaseArea`, and the chain through both providers' `IsProviderFor` with the `1 redundant requirement hidden` note, while its `.cgp.stderr` collapses that to one `[CGP-E001]` block whose `root cause: [CGP-E106] missing field \`base_area\`` tree carries *two* `[CGP-E102]` provider hops (`ScaledArea` then `BaseArea`). -- [`acceptable/providers/higher_order_outer_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/providers/higher_order_outer_dependency.rs) — the mirror, with `base_area` but no `scale_factor`, so the outer layer fails; its `.rust.stderr` pins the single, shorter block, the `HasField` leaf, the caret on `ScaledArea`'s `Self: HasScaleFactor`, and a chain that stops at the outer `IsProviderFor` without naming the inner provider, and its `.cgp.stderr` a `[CGP-E001]` block whose `root cause: [CGP-E106] missing field \`scale_factor\`` tree carries a *single* `[CGP-E102]` hop with the inner provider absent. - -## Related - -- [Check-trait failure (surfaced)](check-trait-failure.md) — the single-layer form of the same surfaced diagnostic; this class is what it looks like when the failing provider wraps another. -- [Verbose dependency cascade](verbose-cascade.md) — the sibling class whose intermediate provider-trait blocks are the same noise this class also emits for an inner failure. -- [`check_components!`](../../reference/macros/check_components.md) and its `#[check_providers(...)]` form — the macro this class is expressed through and the tool that localizes the layer by hand. -- [`IsProviderFor`](../../reference/traits/is_provider_for.md) and [`#[use_provider]`](../../reference/attributes/use_provider.md) — the supertrait that carries each layer's `where` clause into the chain, and the attribute that completes the inner provider's bound. -- [Higher-order providers](../../concepts/higher-order-providers.md) and [Debugging CGP compile errors](../../guides/debugging.md) — the concept this class fails in, and the playbook that prescribes `#[check_providers]`. diff --git a/docs/errors/checks/ordinary-trait-bound.md b/docs/errors/checks/ordinary-trait-bound.md deleted file mode 100644 index a628eb39..00000000 --- a/docs/errors/checks/ordinary-trait-bound.md +++ /dev/null @@ -1,84 +0,0 @@ -# Unsatisfied ordinary trait bound - -A provider's impl-side dependency is an *ordinary* Rust trait bound — a standard-library, foreign, or plain user trait such as `Eq`, `Clone`, `Ord`, or `From`, not a CGP capability — and the concrete type the context supplies for an abstract type or generic parameter does not implement it, so a check surfaces the failure as `E0277` naming that ordinary bound. - -## What triggers it - -This class is the ordinary-trait cousin of a [check-trait failure](check-trait-failure.md): the mechanism is the same lazy wiring, but the unmet leaf is a plain Rust trait rather than a `HasField` or a CGP component. A provider lists a `where`-clause bound that is an ordinary trait applied to an abstract type or an impl generic — `Scalar: Eq`, `Item: Ord`, `T: Clone` — and the context wires a concrete type that does not satisfy it. Because [wiring](../../reference/macros/delegate_components.md) is lazy, the entry is accepted without checking the bound; it fails only when the wiring is exercised. - -The canonical case is an ordinary bound on an abstract type. `CompareScalars` needs the context's `Scalar` type to be `Eq`, and the context wires `Scalar` to `f64`, which is `PartialEq` but not `Eq`: - -```rust -#[cgp_type] -pub trait HasScalarType { - type Scalar; -} - -#[cgp_component(ScalarEquality)] -#[use_type(HasScalarType.Scalar)] -pub trait CanCompareScalars { - fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; -} - -#[cgp_impl(new CompareScalars)] -#[use_type(HasScalarType.Scalar)] -impl ScalarEquality -where - Scalar: Eq, // ordinary trait bound — rewritten to ::Scalar: Eq -{ - fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool { - a == b - } -} - -delegate_components! { - App { - ScalarTypeProviderComponent: UseType, // f64: !Eq - ScalarEqualityComponent: CompareScalars, - } -} -``` - -The same failure arises wherever CGP or Rust accepts a generic trait bound, which is what makes the class broad. An ordinary bound on an **impl generic** in `delegate_components!` behaves identically: a generic context ` Wrapper` that wires its abstract type to `T` carries the provider's `Scalar: Eq` bound as `T: Eq`, unconditionally accepted, and checking `Wrapper` surfaces `f64: Eq` at that one instantiation. The bound may equally sit on an explicit `impl` generic in `#[cgp_impl]`, on a `#[cgp_fn]` `#[extend_where]` clause, or on a `for … in … where` loop — anywhere an ordinary bound rides on a parameter the context ultimately fills. CGP lowers the bound faithfully and cannot see that the wired type violates it, so it defers to the compiler. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class: forcing the wiring through a [`check_components!`](../../reference/macros/check_components.md) produces an `E0277` that names the ordinary bound on the concrete type, and its shape is the tell that distinguishes it from a `HasField` check-trait failure. The **primary** error is the leaf itself — "the trait bound `f64: Eq` is not satisfied," with the caret on the checked component entry and the label "the trait `Eq` is not implemented for `f64`." Beneath it, a `help:` note lists the standard types that *do* implement the trait (`i128`, `i16`, `i32`, …), which is `rustc`'s stock "these types implement the trait" hint for a missing ordinary impl — not the near-contradiction "but trait `HasField` is implemented" hint a missing field produces. - -The position of the cause is the sharp contrast with a [check-trait failure](check-trait-failure.md), and knowing it tells the two apart on sight. A missing `HasField` roots the *primary* error at `Ctx: CanUseComponent` and tucks the concrete leaf into a `help:` note below it; an unmet ordinary bound roots the primary error at the leaf (`f64: Eq`) directly, and the `CanUseComponent` obligation appears only as a `note` further down. Between them runs the same scaffolding: a `note: required for CompareScalars to implement IsProviderFor` that points at the `Scalar: Eq` bound with "unsatisfied trait bound introduced here", then `required for App to implement CanUseComponent<…>`, then the bound in the generated `__CheckApp` trait. [`IsProviderFor`](../../reference/traits/is_provider_for.md) is again the supertrait that carries the provider's `where` clause into the proof, which is why the ordinary bound is named here and suppressed in the hidden form. The whole thing is ordinary `rustc` obligation resolution for [`E0277`](../error_codes/e0277.md); only the leaf's *kind* — a standard trait on a concrete type — changes what the message and its `help:` look like. - -Exercised without a check — by calling the consumer method — the same broken wiring instead produces the [hidden `E0599`](../hidden/unsatisfied-dependency.md), which names only `App: CanCompareScalars` / `App: ScalarEquality` and never mentions `f64: Eq`. The method-probe heuristic drops the nested `where`-clause bound regardless of whether it is a `HasField`, a capability, or an ordinary trait, so the ordinary-bound dependency hides exactly as any other does. - -## Where the root cause is - -The root cause is **present and sits at the very top** of the checked output: the primary `E0277` names the concrete type and the ordinary trait it fails (`f64: Eq`) outright. This is the most direct of the surfaced classes — there is no `help:`-note indirection as with a `HasField` leaf, and the `required for` notes below build *outward* from the leaf toward the check. The caret lands on the checked component entry, so the error also points at a site the user controls. The one thing the diagnostic does not state is which *wired type* is at fault when the bound is on an abstract type: the leaf names `f64`, and the `IsProviderFor` note names the `Scalar: Eq` bound, but connecting "`f64` is what `Scalar` resolves to" is left to the reader — trace it to the `ScalarTypeProviderComponent: UseType` wiring. - -## How cargo-cgp presents it - -`cargo-cgp` treats this class differently from the CGP-capability failures, and the difference is instructive: because the raw primary error already names the ordinary bound at the top, `cargo-cgp` **keeps rustc's headline uncoded** — no `[CGP-Exxx]` tag is stamped, the header stays `error[E0277]: the trait bound \`f64: Eq\` is not satisfied`. What it rewrites is the scaffolding below. The `required for … IsProviderFor` / `CanUseComponent` / `__CheckApp` chain collapses into a compact dependency tree — `[CGP-E101]` consumer-trait hop → `[CGP-E102]` provider-trait hop → the leaf — where the ordinary bound rides as an uncoded pass-through (`the trait bound \`f64: Eq\` is not satisfied`) because it is a plain Rust trait, not a CGP construct the tool renders. This is the one surfaced check class with no `[CGP-E0xx]` headline: the tool leaves the already-clear `rustc` sentence alone and only tidies the frames beneath it. - -The contrast with the [hidden method-call form](../hidden/unsatisfied-dependency.md) sharpens the point. There the raw `E0599` buries the bound entirely, so `cargo-cgp` must *recover* it and does stamp a headline — `[CGP-E001]` over a `root cause: [CGP-E201] the trait bound \`f64: Eq\` is not satisfied` tree, `[CGP-E201]` being the dedicated root-cause lead for an ordinary bound whose terminal is uncoded. A code appears there because the tool reclassified a hidden error; none appears here because the surfaced error was already its own clearest statement. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -The fix is to satisfy the ordinary trait, which is a different remedy from every capability-dependency class — not "wire a component" or "add a field," but make the concrete type implement the trait. Wire the abstract type (or instantiate the generic) to a type that *does* satisfy the bound — an integer type for `Eq`, say, instead of `f64` — or, when the offending type is one you own, derive or implement the trait for it (`#[derive(Eq)]`, a manual `impl Ord`). When the bound is stricter than the behavior needs — requiring `Eq` where `PartialEq` would do — relax the provider's `where` clause instead, so more concrete types qualify. The diagnostic already names both halves: the concrete type in the primary error and the bound in the `IsProviderFor` note. - -## Notes for tooling - -One usability gap remains beyond what `cargo-cgp` already collapses: the tree stops at the ordinary bound (`f64: Eq`) but does not **link the concrete type back to the wiring that chose it**. Following the abstract-type component to its `UseType<…>` entry — to report "`Scalar` is wired to `f64`, which the provider `CompareScalars` requires to be `Eq`" — is the connective step still left to the reader, since the passed-through leaf names `f64` without saying it is what `Scalar` resolves to. - -## Backing fixtures - -- [`acceptable/resolution/ordinary_bound_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/resolution/ordinary_bound_unsatisfied.rs) — a provider requiring `Scalar: Eq` on a context that wires `Scalar` to `f64`, checked; its `.rust.stderr` pins the primary `E0277` on `f64: Eq`, the `help:` list of conforming standard types, and the `IsProviderFor` note pointing at the `Scalar: Eq` bound, while its `.cgp.stderr` keeps that same uncoded headline and rewrites only the chain into the `[CGP-E101]`/`[CGP-E102]` tree over the pass-through leaf. -- [`acceptable/generic/generic_context_ordinary_bound.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/generic/generic_context_ordinary_bound.rs) — the same bound reached through impl generics: a generic ` Wrapper` table checked at `Wrapper`, surfacing `f64: Eq` through `IsProviderFor<…, Wrapper>`, with the `.cgp.stderr` tree naming the context as `Wrapper`. - -The hidden (method-call) form of the same mistake is [`acceptable/use-site/ordinary_bound_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/ordinary_bound_unsatisfied.rs), cataloged with the [hidden unsatisfied dependency](../hidden/unsatisfied-dependency.md) class it shares its shape with; there the `.cgp.stderr` must recover the buried bound, so it does lead with `[CGP-E001]` and a `root cause: [CGP-E201]` tree. - -## Related - -- [Check-trait failure (surfaced)](check-trait-failure.md) — the sibling surfaced class whose leaf is a CGP capability (`HasField`, a component); contrast the leaf's *kind* (an ordinary trait here) and its *position* (the primary error here, a `help:` note there). -- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the `E0599` form this takes when reached by a method call, where the ordinary bound is suppressed just like any other leaf. -- [Verbose dependency cascade](verbose-cascade.md) — when several providers share the unmet ordinary bound, this diagnostic multiplies the same way. -- [`E0277`](../error_codes/e0277.md) — the Rust error code this class is reported under, and its `Sized` special case. -- [`#[use_type]`](../../reference/attributes/use_type.md), [`#[cgp_type]`](../../reference/macros/cgp_type.md), and [`check_components!`](../../reference/macros/check_components.md) — the abstract-type import, the abstract-type component, and the check that surfaces the bound. -- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0277` entry in the decoder. diff --git a/docs/errors/checks/unregistered-namespace-path.md b/docs/errors/checks/unregistered-namespace-path.md deleted file mode 100644 index 75bff2f1..00000000 --- a/docs/errors/checks/unregistered-namespace-path.md +++ /dev/null @@ -1,86 +0,0 @@ -# Unregistered namespace path - -A context joins a namespace that *routes* a component to a path but never *binds* a provider at that path, so the lookup finds no delegate — and a check surfaces the failure as an `E0277` naming the path-keyed `DefaultNamespace` (or `DelegateComponent`) bound that has no impl. - -## What triggers it - -This class is a *lookup* failure rather than a *dependency* failure: no provider is found for the component at all, as opposed to a provider being found whose `where` clause is unmet. It arises when a component is registered into a [namespace](../../reference/macros/cgp_namespace.md) under a path — through [`#[prefix]`](../../reference/macros/cgp_namespace.md) — and a context joins that namespace, but nothing ever terminates the path with a concrete provider. The prefix registers the *routing* (the namespace resolves the component to a `RedirectLookup` along the path), yet the path itself stays empty because the author forgot the `#[default_impl]`, the namespace body entry, or the direct wiring that would place a provider there. - -```rust -#[cgp_component(Greeter)] -#[prefix(@app in DefaultNamespace)] // routes GreeterComponent to @app.GreeterComponent -pub trait CanGreet { - fn greet(&self) -> String; -} - -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) -> String { "Hello".to_owned() } -} - -pub struct App; - -// App joins the namespace, so GreeterComponent follows the @app.GreeterComponent -// redirect — but nothing (no #[default_impl], no body entry, no direct line) ever -// binds GreetHello, or anything, at that path. -delegate_components! { - App { - namespace DefaultNamespace; - } -} - -check_components! { - App { - GreeterComponent, - } -} -``` - -The same shape appears whenever a redirect lands on an empty path: a context whose joined namespace does not inherit the base namespace a component was prefixed into, a `@`-path entry with a mistyped or too-short segment, or a component prefixed under one path but wired under another. In every case the redirect resolves to a table slot that holds nothing. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class, and unusually the root cause *is the primary error* rather than a note beneath it. Forcing the wiring through a [`check_components!`](../../reference/macros/check_components.md) produces an `E0277` whose headline is that the *path* does not implement the namespace lookup trait — `PathCons>: DefaultNamespace` is not satisfied — with the caret on the checked `GreeterComponent` entry. The failing bound's `Self` is a `PathCons<…>` path, not a bare component marker, and that is the signature of the class: the compiler is reporting that the redirect target has no table entry. - -Two notes below the headline confirm the reading and are worth recognizing. A `note: required for App to implement DelegateComponent>` names the exact missing table entry — the path for which `App` has no delegate — and a `note: required for RedirectLookup> to implement IsProviderFor` names the [`RedirectLookup`](../../reference/providers/redirect_lookup.md) provider the redirect resolved to. A `Self` that is a `RedirectLookup` or a `PathCons` path, rather than a real provider struct, always means the failure is in the lookup, not in a provider's dependencies. - -One landmark reads at first like a contradiction and is in fact diagnostic. Beneath the headline the compiler prints `help: the following other types implement trait DefaultNamespace:` and lists the component *markers* that the namespace does resolve — including the bare `GreeterComponent` marker. That the marker implements `DefaultNamespace` while the *path* `PathCons` does not tells you the `#[prefix]` registration succeeded — the component is in the namespace — but the path it routes to was never filled. The problem is a missing binding at the leaf, not a missing prefix. - -The diagnostic itself is ordinary `rustc` trait-solving, which is worth seeing so the shape is not mistaken for something exotic. [`E0277`](../error_codes/e0277.md) is the code for an unsatisfied trait bound, and everything here is standard obligation resolution. Proving the check's `App: CanUseComponent` obligation drives the solver through the `RedirectLookup` provider, which implements the component's provider trait only `where Components: DelegateComponent` — so the solver must discharge `App: DelegateComponent>`, finds no impl at that path, and reports *that* bound as the unsatisfied one. The `required for …` lines are `rustc`'s ordinary obligation-tracing output for the proof, and the `help: the following other types implement trait …` list is its stock "an impl of the same trait exists" hint, pointing at the keys the namespace *does* resolve. What makes this class read as "surfaced at the top" rather than buried is only that the unmet bound is a *lookup* (`DelegateComponent`/`DefaultNamespace`) with no impl at all, so it is the first obligation to fail — unlike a [check-trait failure](check-trait-failure.md), where a provider *is* found and the solver descends into its `where` clause before reporting the leaf in a `help:` note. - -## Where the root cause is - -The root cause is **present and sits at the top of the output**, in the primary `E0277` line and its first `required for … DelegateComponent>` note. This is the opposite position from a [check-trait failure](check-trait-failure.md), whose concrete leaf lives in a `help:` note *below* the primary error; here the unsatisfied lookup bound *is* the primary error, and the `required for` notes build outward from it toward the check. Reading the `PathCons<…>` key of that bound tells you which path is unbound — decode it with the [`Symbol!`](../../reference/macros/symbol.md) segments, or read the `long-type-….txt` file the compiler names when the path is elided as `...`. - -Exercised without a check — by calling the consumer-trait method on the context — the same broken wiring instead produces the [hidden `E0599`](../hidden/unsatisfied-dependency.md): "the method `greet` exists for struct `App`, but its trait bounds were not satisfied," naming only `App: CanGreet`/`App: Greeter` and descending no further. That output is byte-for-byte the shape of the hidden unsatisfied-dependency class, because the compiler's method-probe heuristic drops the nested lookup bound exactly as it drops a nested dependency bound. So an unregistered path is hidden when reached by a call and surfaced when reached by a check, and only the check reveals which path is empty. - -## How cargo-cgp presents it - -`cargo-cgp` recasts the raw lookup failure from the machinery's point of view to the programmer's: it keeps the `E0277` code but replaces the `PathCons<…>: DefaultNamespace` headline with `[CGP-E001] the consumer trait \`CanGreet\` is not implemented for context \`App\``, then states the empty path in a `root cause: [CGP-E107] context \`App\` does not contain any delegate entry for \`@app.GreeterComponent\`` note. The tree between them is short — `[CGP-E101]` consumer-trait hop → `[CGP-E104]` redirect-lookup hop naming the path → the `[CGP-E107]` leaf — so the `RedirectLookup` and `DelegateComponent` scaffolding of the raw form collapses into one readable "redirect lands on an empty slot" chain. The `PathCons` spine is resugared to its surface `@app.GreeterComponent` notation throughout, which is the single biggest readability win over the raw output, where the path is an abbreviated `PathCons>, _>`. - -The shape scales with how the redirect fails. A deep prefix renders its whole path (`@app.finance.types.QuantityTypeProviderComponent`); a chain of redirects that never terminates prints one `[CGP-E104]` hop per link before the `[CGP-E107]` leaf (`@start.GreeterComponent` → `@middle` → `@end`); and an `open` per-type dispatch missing a key names the type in the path (`@ItemEncoderComponent.Vec`). In every case the leaf is `[CGP-E107]` — the context wires no provider at the path the component routes to. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -The fix is to bind a provider at the path the component routes to. Register it from the provider's own definition with [`#[default_impl(@app.GreeterComponent in Namespace)]`](../../reference/traits/default_namespace.md), add a namespace **body** entry (`@app.GreeterComponent: GreetHello`) in the namespace's own crate, or wire the path directly on the context (`@app.GreeterComponent: GreetHello`) — whichever matches where the wiring belongs, per the [namespaces guide](../../guides/namespaces-and-prefixes.md). When the path itself is wrong — a prefix and a wiring that disagree on the path, or a joined namespace that does not inherit the base the component was prefixed into — the fix is to reconcile the two so the redirect and the binding name the same path. - -A related variant resolves to a delegate that exists but is not a provider for the component: a path wired to the wrong provider produces an `E0277` where the `RedirectLookup`'s delegate — a named provider — fails to implement the component's provider trait, rather than the `DelegateComponent` lookup failing outright. The remedy there is to wire the path to a provider that actually implements the component. - -## Notes for tooling - -`cargo-cgp` already extracts the unbound path — the `[CGP-E107]` leaf decodes the `PathCons` key back to `@app.GreeterComponent` and reports that the context wires nothing there. What remains for a tool is the **fix hint** the raw `help:` list makes available but `[CGP-E107]` does not spell out: because the bare component marker still resolves through the namespace while the path does not, the registration succeeded and only the leaf binding is missing — so the actionable advice "you likely forgot a `#[default_impl]` or body entry" could be added rather than left for the reader to infer. - -## Backing fixtures - -- [`acceptable/resolution/unregistered_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/resolution/unregistered_prefix_path.rs) — a `#[prefix]`-ed `Greeter` joined through `DefaultNamespace` with no provider ever bound at `@app.GreeterComponent`; its `.rust.stderr` pins the primary `E0277` on `PathCons<…>: DefaultNamespace`, the `required for … DelegateComponent>` note that names the empty path, the `RedirectLookup … IsProviderFor` note, and the `help:` list in which the bare `GreeterComponent` marker appears, while its `.cgp.stderr` recasts this as `[CGP-E001]` over a `root cause: [CGP-E107] … @app.GreeterComponent` tree with a `[CGP-E104]` redirect hop. -- [`acceptable/wiring/namespace-paths/qualified_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/qualified_prefix_path.rs) — a deep prefix whose `[CGP-E107]` leaf renders the full `@app.finance.types.QuantityTypeProviderComponent` path, pinning that the resugaring reproduces multi-segment paths intact. -- [`acceptable/wiring/namespace-paths/multi_redirect_missing.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/multi_redirect_missing.rs) — a chain of redirects that never terminates, so the `.cgp.stderr` tree stacks one `[CGP-E104]` hop per link (`@start.GreeterComponent` → `@middle` → `@end`) before the `[CGP-E107]` leaf at `@end`. -- [`acceptable/wiring/namespace-paths/open_missing_type_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/open_missing_type_key.rs) — an `open` per-type dispatch missing a type key, where the `[CGP-E107]` leaf names the type in the path (`@ItemEncoderComponent.Vec`) and the consumer headline is the generic `CanEncodeItem>`. - -## Related - -- [Check-trait failure (surfaced)](check-trait-failure.md) — the sibling surfaced class, where a provider *is* found but its dependency is unmet; contrast the position of the cause (a `help:` note there, the primary error here) and the failing bound (a concrete `HasField` there, a `PathCons` lookup here). -- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the `E0599` shape this class takes when exercised by a method call instead of a check; a missing wiring and an unmet dependency are indistinguishable in that hidden form. -- [Verbose dependency cascade](verbose-cascade.md) — when several components route through the same empty path, this diagnostic multiplies the same way. -- [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md), [`RedirectLookup`](../../reference/providers/redirect_lookup.md), [`DefaultNamespace`](../../reference/traits/default_namespace.md), and the [namespaces guide](../../guides/namespaces-and-prefixes.md) — the routing mechanics and the wiring that fills a path. -- [Debugging CGP compile errors](../../guides/debugging.md) — the `DelegateComponent`/`Namespace` "lookup failed" entry in the decoder. diff --git a/docs/errors/checks/verbose-cascade.md b/docs/errors/checks/verbose-cascade.md deleted file mode 100644 index 2056dcba..00000000 --- a/docs/errors/checks/verbose-cascade.md +++ /dev/null @@ -1,70 +0,0 @@ -# Verbose dependency cascade - -One mistake deep in a dependency graph surfaces at every provider that transitively needs it, so the compiler prints far more error blocks than there are mistakes — and the reader's task is to find the one root cause among the repeats. - -## What triggers it - -This class is the *volume* form of a [surfaced check-trait failure](check-trait-failure.md): it arises whenever several components share a transitive dependency and that dependency is unmet. A chain of providers — `ProvideBaz` depends on `CanBar`, `ProvideBar` on `CanFoo`, `ProvideFoo` on a `name` field — is wired onto a context missing that field, and all three components are checked together. - -```rust -#[cgp_impl(new ProvideFoo)] -impl Foo where Self: HasName { /* needs the `name` field */ } - -#[cgp_impl(new ProvideBar)] -#[uses(CanFoo)] // depends on Foo -impl Bar { /* … */ } - -#[cgp_impl(new ProvideBaz)] -#[uses(CanBar)] // depends on Bar -impl Baz { /* … */ } - -#[derive(HasField)] -pub struct App { pub age: u8 } // no `name` field - -delegate_components! { - App { FooComponent: ProvideFoo, BarComponent: ProvideBar, BazComponent: ProvideBaz } -} - -check_components! { - App { BazComponent, BarComponent, FooComponent } -} -``` - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the reshaped form. The output is a cascade whose block count reflects the depth of the graph, not the number of mistakes. Checking three chained components over one missing field produces **six** `E0277` blocks, not three, and they are of two kinds. Each checked component yields a `CanUseComponent<…Component>` block that *does* reach the concrete root cause — its `help:` note names `HasField` as not implemented for `App` (and points at the nearest existing field, `HasField`, as a landmark). But the deeper components additionally emit **intermediate** blocks — `ProvideBar: Bar` is not satisfied, `ProvideFoo: Foo` is not satisfied — that name only an inner provider trait and never descend to the field. The intermediate blocks are the noise; the `CanUseComponent` blocks are the signal. - -Two pieces of `rustc` machinery shape the cascade and are worth recognizing so they are not misread. Each intermediate block carries the [near-contradiction](../../guides/debugging.md) shape — `help: the trait Bar is not implemented for ProvideBar` immediately followed by `help: the trait Bar<__Context__> is implemented for ProvideBar` — which is `rustc`'s "a similar impl exists" hint reporting that the *generic* provider impl (over `__Context__`) exists but does not hold for the concrete `App`, because a bound deeper in its chain fails. It does not mean the impl is both present and absent; it means the impl is present but inapplicable here. And the longer chains end with `= note: N redundant requirements hidden`, `rustc` collapsing repeated identical obligations it has already printed, so the elision is a sign the chain revisits the same bound, not that a distinct cause was omitted. - -The hidden analogue exists too: exercising the same chain through direct consumer-trait *calls* rather than a check produces one `E0599` per call, none of which reaches the cause — the [hidden](../hidden/unsatisfied-dependency.md) failure, multiplied. This document covers the surfaced (checked) cascade, where the cause is present but buried in volume. - -## Where the root cause is - -The root cause is **present and repeated** — the same `HasField` appears in every `CanUseComponent` block's `help:` note. The reliable way to find it is not positional but structural: **look for a block whose `help:` names a concrete missing item — a `HasField`, an abstract type — rather than a provider trait like `ProvideFoo: Foo`.** Those concrete-item blocks all point at the one thing to fix; the provider-trait blocks are intermediate consequences. When components are checked top-down (foundational last, as in the fixture), the final block is a clean concrete-item block, which is why the root cause is *often* found near the last or second-to-last message — but the concrete-item test is what makes it reliable regardless of order. - -## How cargo-cgp presents it - -`cargo-cgp` collapses the whole cascade to a single block. First it **drops every intermediate provider-trait block** as derived noise and recovers each *checked* component's failure to a `root cause:` tree that descends all the way to the leaf. Then it **coalesces** the checked components that share that leaf: rather than one block per component, it emits one `[CGP-E001]` headline naming every affected consumer trait — `` the consumer traits `CanBaz`, `CanBar`, and `CanFoo` are not implemented for context `App` `` — with a caret at each check entry and a single `root cause: [CGP-E106] missing field \`name\`` tree beneath. So a six-block raw cascade over three checked components becomes one block, and cargo's re-count reports one error. A component rustc happened to surface provider-side (a lone `[CGP-E002]` block) is worded uniformly as a consumer in the merged headline, since a `check_components!` entry failing *is* the consumer trait failing. - -Listing every affected consumer in one headline is possible because the tool holds the compilation's diagnostics until they have all arrived (there is no end-of-compilation hook), then groups together the ones that *share* a root cause. Grouping on a shared cause rather than on a whole-failure key is what also reaches the harder shape, where one omission is reached at several instantiations and so surfaces as several root causes: each check entry stops at the first unmet leaf on its own branch while a use-site call reaches them all, so no two of those cause sets are equal and grouping on equality left one mistake reported as several blocks. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -Fix the single root cause and the entire cascade collapses at once — supply the `name` field, and all six blocks disappear together. The difficulty is isolation, not repair, so the [debugging guide](../../guides/debugging.md) prescribes two moves: **check one suspect component in isolation** in its own `check_components!` block, which strips the cascade down to that component's chain, or **bisect** a large table by commenting entries out until the failure vanishes. Both converge on the one broken link far faster than reading the cascade top to bottom. - -## Notes for tooling - -`cargo-cgp` reshapes this class in full, as [How cargo-cgp presents it](#how-cargo-cgp-presents-it) describes: it drops the intermediate provider-trait blocks, recovers each checked component's innermost concrete cause, and coalesces the components that share one leaf into a single headline that lists every affected consumer over one root-cause tree. The coalescing keys on the root causes themselves, consumer-independently and one cause at a time, so it generalizes to any set of checked components bottoming out on the same root cause — not just a field, and not only components whose causes match exactly, since failures whose cause sets merely overlap are grouped too. - -## Backing fixtures - -- [`acceptable/duplication/dependency_cascade.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/duplication/dependency_cascade.rs) — a three-deep provider chain over one missing `name` field, checked across all three components; its `.rust.stderr` pins the six-block raw cascade (the concrete `HasField` in each `CanUseComponent` block's `help:` note, and the intermediate `ProvideFoo: Foo` / `ProvideBar: Bar` noise blocks), while its `.cgp.stderr` shows the single coalesced block — `` the consumer traits `CanBaz`, `CanBar`, and `CanFoo` `` over one `[CGP-E106] missing field \`name\`` tree, with a caret at each entry. -- [`acceptable/duplication/density_3.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/duplication/density_3.rs) — a denser two-component check over one missing `height` field where the second component (`CanCalculateDensity`) depends transitively on the first (`CanCalculateArea`); its `.cgp.stderr` pins the two components coalesced into one `[CGP-E001]` headline over the shared `[CGP-E106]` leaf. - -## Related - -- [Check-trait failure (surfaced)](check-trait-failure.md) — the single-cause diagnostic this class multiplies. -- [Higher-order provider layer failure (surfaced)](higher-order-provider-layer.md) — a sibling class that emits the same intermediate provider-trait noise blocks, there to distinguish an inner-layer failure from an outer one. -- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the same root cause seen through consumer-trait calls, where each block hides the cause instead of surfacing it. -- [Debugging CGP compile errors](../../guides/debugging.md) — why the error count tells you nothing about the number of mistakes, and how to isolate the one link. -- [`check_components!`](../../reference/macros/check_components.md) and [`IsProviderFor`](../../reference/traits/is_provider_for.md). diff --git a/docs/errors/error_codes/README.md b/docs/errors/error_codes/README.md deleted file mode 100644 index 5344c546..00000000 --- a/docs/errors/error_codes/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Rust error-code reference - -This directory is a compact reference for the handful of `rustc` error codes CGP's post-codegen failures surface under, so a reader who has an error code in hand can look up what it means in plain Rust — the message `rustc` prints, the language rule behind it, and the RFC or issue that defines that rule — before turning to the CGP-specific class that produced it. It is the *forward* index (error code → meaning → the CGP classes that emit it) that complements the [catalog's class documents](../README.md), which run the other way (a CGP mistake → the diagnostic it produces). - -## Why this exists - -The per-class documents in the catalog each explain one CGP failure, and several of them lean on the same underlying Rust rule — coherence, the orphan rule, the trait-solver recursion limit. Rather than restate that rule in every class that touches it, each code has one entry here that states it once, grounded in the official Rust documentation and the RFCs and issues that define it, and the class documents link to that entry. This keeps the Rust-language facts in one verified place and the CGP-specific anatomy in the class docs, so neither drifts and neither repeats the other. - -Each entry records the same four things: the message `rustc` prints, when the compiler emits it in ordinary Rust, the rule it enforces and where that rule is defined, and which CGP error classes produce it. The facts here are verified against the official [error index](https://doc.rust-lang.org/error_codes/), the [Rust reference](https://doc.rust-lang.org/reference/), and the linked RFCs and `rust-lang/rust` issues, not against memory; the class documents remain the source of truth for the *exact* diagnostic a CGP mistake produces, pinned by their [`cargo-cgp` UI fixtures](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md). - -## The codes - -The catalog surfaces nine codes, split by the kind of rule they enforce. Three are **coherence and orphan** rules — the compiler refusing overlapping or foreign impls: - -- [`E0119`](e0119.md) — conflicting implementations of a trait for a type. -- [`E0210`](e0210.md) — orphan rule: an uncovered type parameter in a foreign-trait impl. -- [`E0117`](e0117.md) — orphan rule: a foreign trait implemented for a foreign (arbitrary) type. - -One is a **well-formedness** rule on impl parameters: - -- [`E0207`](e0207.md) — an unconstrained type parameter on an impl. - -Two are **trait-solving** outcomes — the solver failing to satisfy or terminate a bound: - -- [`E0277`](e0277.md) — a trait bound is not satisfied (including the `Sized` special case). -- [`E0275`](e0275.md) — overflow evaluating a requirement (a recursion-limit or cycle failure). - -Three are **name-resolution and method-probe** diagnostics: - -- [`E0428`](e0428.md) — a name is defined more than once in one scope. -- [`E0576`](e0576.md) — an associated item is named that the trait or type does not declare. -- [`E0599`](e0599.md) — a method exists but its trait bounds are not satisfied. - -Beyond the `rustc` codes, `cargo-cgp` stamps its own `[CGP-Exxx]` codes on a message it rewrites into a recognized CGP class. Those are `cargo-cgp`'s, not CGP's, so they are cataloged in one pointer entry rather than mixed in above: - -- [cargo-cgp-codes.md](cargo-cgp-codes.md) — the headline (`CGP-E0xx`), dependency-tree (`CGP-E1xx`), and root-cause-lead (`CGP-E2xx`) codes, each with a one-line meaning and a link to `cargo-cgp`'s authoritative [error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Relationship to the rest of the catalog - -These entries are a supporting reference, not a class of error: they describe Rust, and the [class documents](../README.md) describe CGP. A code entry's "Where CGP produces it" section links out to every class that emits it, and each class links back to the code entry in place of the raw `doc.rust-lang.org` URL, so the official links live here and the classes cite this directory. The rules for keeping these entries accurate — verifying wording against a real compilation and the official docs rather than memory — are the same [synchronization rules](../AGENTS.md) that govern the whole catalog. diff --git a/docs/errors/error_codes/cargo-cgp-codes.md b/docs/errors/error_codes/cargo-cgp-codes.md deleted file mode 100644 index f69d644e..00000000 --- a/docs/errors/error_codes/cargo-cgp-codes.md +++ /dev/null @@ -1,50 +0,0 @@ -# cargo-cgp `[CGP-Exxx]` codes - -This entry catalogs the `[CGP-Exxx]` codes [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) stamps on a diagnostic it rewrites into a recognized CGP class, so a class document's *How cargo-cgp presents it* section can cite a code by number. Unlike the sibling `rustc`-code entries, these codes are **owned by `cargo-cgp`, not by CGP** — this file is a pointer, and the authoritative definitions (what triggers each code and how to fix it) live in `cargo-cgp`'s [error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). When the two disagree, that catalog wins; keep this list in sync with it under the [cross-project rule](../../../AGENTS.md#the-two-projects-cgp-and-cargo-cgp). - -## What a code is - -A code is `CGP-E` plus three digits, shown in square brackets at the start of a rewritten message — for example `error[E0277]: [CGP-E001] the consumer trait \`CanCalculateArea\` is not implemented for context \`Rectangle\``. The diagnostic keeps its own `rustc` code (`E0277`, `E0599`, `E0271`, `E0275`), so `rustc --explain` still works and the entries in this directory still apply; the `[CGP-Exxx]` tag rides *inside* the message text. A code is assigned only when `cargo-cgp` both rewrote the message and identified it as a CGP class — everything else is left uncoded by design. - -The three-digit space is split by *what* a code classifies. - -## Main-message headlines — `CGP-E0xx` - -These lead a rewritten main message. `E001`–`E003` and `E009` come from the typed resolver and carry a `root cause:` note; `E004`–`E008` classify a duplicate-key `E0119` conflict (no root-cause note, both `rustc` carets kept); `E010` is a wiring-overflow rewrite of `E0275`. - -- **CGP-E001** — the consumer trait is not implemented for the context (missing wiring, or an unmet transitive dependency). -- **CGP-E002** — the provider trait is not implemented for the provider (its impl-side `where` bounds do not hold). -- **CGP-E003** — a context field has the wrong type (the `HasField` bound holds but its `::Value` projection fails). -- **CGP-E004** — duplicate wiring: the same key mapped twice. -- **CGP-E005** — overlapping wiring: a key already set through another (generic over specific, `@`-path over a namespace forwarding, or a prefix path). -- **CGP-E006** — multiple namespaces used for one target in `delegate_components!`. -- **CGP-E007** — redirect collision: a direct wiring collides with a redirect of the same key. -- **CGP-E008** — duplicate redirect: the same key redirected more than once. -- **CGP-E009** — a hand-written wrapper trait (not a CGP consumer) fails because a CGP component it depends on fails. -- **CGP-E010** — the wiring never resolves (an `E0275` overflow whose requirement is a `CanUseComponent` bound; usually a `UseContext` cycle). - -## Dependency-tree entries — `CGP-E1xx` - -One per rendering template, riding at the start of each entry in a `root cause:` dependency tree. - -- **CGP-E101** — consumer trait impl hop. -- **CGP-E102** — provider trait impl hop. -- **CGP-E103** — non-terminal `HasField` accessor hop. -- **CGP-E104** — redirect-lookup hop (a namespace or `open` `RedirectLookup`). -- **CGP-E105** — a hop through any other trait impl (a user capability, or an ordinary bound restated). -- **CGP-E106** — leaf: a genuinely missing context field. -- **CGP-E107** — leaf: the context wires no provider for a component (or terminates no namespace path). -- **CGP-E108** — leaf: the struct has the field but did not derive `HasField`. -- **CGP-E109** — leaf: a field has the wrong type. -- **CGP-E110** — leaf: a non-context delegation table (aggregate provider, `UseDelegate`/`UseInputDelegate`) is missing a key. -- **CGP-E111** — leaf: a non-provider was wired into a provider slot. - -## Root-cause leads — `CGP-E2xx` - -The `root cause:` line reuses the terminal leaf's `CGP-E1xx` code where it has one; this range exists for the one case that needs its own. - -- **CGP-E201** — the failure bottoms out on an ordinary (non-CGP) trait bound. - -## Where CGP classes cite these - -A class document's *How cargo-cgp presents it* section names the specific code(s) the tool produces for that class and links to the [authoritative catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). For example, [check-trait-failure](../checks/check-trait-failure.md) is stamped `[CGP-E001]` with a `root cause:` tree whose leaf is `[CGP-E106]` (missing field) or `[CGP-E108]` (underived); the [conflicting-wiring](../wiring/conflicting-wiring.md) family maps to `[CGP-E004]`–`[CGP-E008]`; and [wiring-cycle](../wiring/wiring-cycle.md) maps to `[CGP-E010]`. diff --git a/docs/errors/error_codes/e0117.md b/docs/errors/error_codes/e0117.md deleted file mode 100644 index 16b842f3..00000000 --- a/docs/errors/error_codes/e0117.md +++ /dev/null @@ -1,21 +0,0 @@ -# E0117 — orphan rule: foreign trait for an arbitrary type - -`E0117` is the orphan rule's error for implementing a *foreign* trait for a *foreign* type when no local type is involved at all — the impl is wholly foreign, so this crate may not write it. - -## What it means - -The compiler prints "only traits defined in the current crate can be implemented for arbitrary types", with the canonical example `impl Drop for u32 {}` — both `Drop` and `u32` are foreign. It enforces the same **orphan rule** as [`E0210`](e0210.md), the cross-crate half of coherence, and the two differ only in shape: `E0117` fires when the offending types are foreign *concrete* (nominal) types with no local type anywhere, while `E0210` fires when a *type parameter* is left uncovered. In both, the fix is that at least one local type must take part in the impl. - -## The rule behind it - -The orphan rule exists so that coherence holds across crates: were a crate allowed to implement a foreign trait for a foreign type, two crates could do so incompatibly and adding a dependency could break a build. Requiring a local type in the impl guarantees the impl "belongs" to a crate that owns one of its pieces. The rule comes from [RFC 2451 (re-rebalancing coherence)](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html), building on RFC 1023; the [`E0210`](e0210.md) entry describes the parameter-covering ordering that the rule also imposes when type parameters are present. Which of the two codes `rustc` emits for a given foreign-trait impl depends on whether an uncovered parameter is involved, and the boundary between them is a known `rustc` wart, so treat the pair as one rule with two messages. - -## Where CGP produces it - -- [Orphan-rule violation](../wiring/orphan-rule.md) — the same namespace-registration mistakes as [`E0210`](e0210.md); which code appears depends on the exact shape of the generated impl, but both are the orphan rule rejecting a foreign-trait-for-foreign-type registration. - -## References - -- [Error index: E0117](https://doc.rust-lang.org/error_codes/E0117.html) -- [RFC 2451 — re-rebalancing coherence](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) -- [Error index: E0210](https://doc.rust-lang.org/error_codes/E0210.html) — the sibling orphan error for an uncovered type parameter diff --git a/docs/errors/error_codes/e0119.md b/docs/errors/error_codes/e0119.md deleted file mode 100644 index 9c9081a6..00000000 --- a/docs/errors/error_codes/e0119.md +++ /dev/null @@ -1,24 +0,0 @@ -# E0119 — conflicting implementations - -`E0119` is Rust's coherence error: two trait implementations could both apply to the same type, so the compiler rejects the pair rather than pick one. - -## What it means - -The compiler prints "conflicting implementations of trait `MyTrait` for type `Foo`", with two carets — "first implementation here" on the earlier impl and "conflicting implementation for `Foo`" on the later one. It fires whenever two impls overlap: two impls for the same concrete type, or — the common CGP shape — a blanket impl (`impl Trait for T where …`) whose applicability overlaps another impl. The rule it enforces is **coherence**: for any trait and type there must be at most one applicable impl, so that a method call resolves unambiguously. - -## The rule behind it - -Coherence reasons not only about the impls in scope but about impls other crates *could add*, which is why some `E0119`s carry an extra note. When a blanket impl's applicability to a type hinges on a trait bound a downstream crate could later satisfy, the compiler cannot prove the two impls will never overlap, so it rejects them up front and cites the hypothetical impl: `note: downstream crates may implement trait `…` for type `…`` (or the `upstream crates may add a new impl … in future versions` variant). This future-compatibility, "negative reasoning" behavior is what makes adding a blanket impl a potential breaking change, and it is formalized by [RFC 2451 (re-rebalancing coherence)](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html), building on RFC 1023. A conflict the compiler can prove *directly* from the impls in front of it — two blanket impls over the same key, say — needs no such reasoning and prints without the downstream note. - -## Where CGP produces it - -CGP emits overlapping impls whenever a key or a blanket forwarding is wired twice, and the catalog splits these by usage: - -- [Conflicting wiring](../wiring/conflicting-wiring.md) — the same key or generated name wired or declared twice (plus [`E0428`](e0428.md) for a duplicate name). -- [Overlapping namespace forwarding](../wiring/namespace-forwarding-conflict.md) — two blanket forwarding impls over every key (fully-generic types, no downstream note). -- [Namespace override conflict](../wiring/namespace-override-conflict.md) — a specific entry overriding a key a namespace already claims (the context-level shape carries the downstream note). - -## References - -- [Error index: E0119](https://doc.rust-lang.org/error_codes/E0119.html) -- [RFC 2451 — re-rebalancing coherence](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) (the orphan and coherence rules, and the downstream/upstream negative reasoning) diff --git a/docs/errors/error_codes/e0207.md b/docs/errors/error_codes/e0207.md deleted file mode 100644 index 1719244c..00000000 --- a/docs/errors/error_codes/e0207.md +++ /dev/null @@ -1,20 +0,0 @@ -# E0207 — unconstrained type parameter - -`E0207` is Rust's rule that every generic parameter on an impl must be *determined* by the impl — appearing in the trait, the self type, or a predicate that pins it — so a parameter that reaches only an associated-type value is rejected. - -## What it means - -The compiler prints "the type parameter `T` is not constrained by the impl trait, self type, or predicates". An impl parameter must occupy at least one of three "constrained" positions: it must appear in the **implemented trait** reference, in the **self type**, or in a **`where`-clause predicate** that binds it as an associated type (`where T: Trait`). A parameter that appears only on the right of an associated-type equality — the *value* it projects to — does not constrain the impl, because a trait reference to that impl names no `T`, so any `T` would satisfy the header equally. - -## The rule behind it - -The requirement exists because impls cannot be named or instantiated explicitly, so an unconstrained parameter has no basis for inference and yields ill-defined semantics; it would also break coherence, since a downstream crate could add another value for the same parameter and make the choice ambiguous. This is the same negative-reasoning concern that underlies the orphan rule and [`E0119`](e0119.md). The rule comes from [RFC 447 (no unused impl parameters)](https://github.com/rust-lang/rfcs/blob/master/text/0447-no-unused-impl-parameters.md). - -## Where CGP produces it - -- [Unconstrained generic](../wiring/unconstrained-generic.md) — a per-entry generic in `delegate_components!` that appears only in the provider value and never reaches the key, or a generic provider registered as a per-type default, so the parameter lands only in the `Delegate` associated-type position. - -## References - -- [Error index: E0207](https://doc.rust-lang.org/error_codes/E0207.html) -- [RFC 447 — no unused impl parameters](https://github.com/rust-lang/rfcs/blob/master/text/0447-no-unused-impl-parameters.md) diff --git a/docs/errors/error_codes/e0210.md b/docs/errors/error_codes/e0210.md deleted file mode 100644 index a76d9742..00000000 --- a/docs/errors/error_codes/e0210.md +++ /dev/null @@ -1,21 +0,0 @@ -# E0210 — orphan rule: uncovered type parameter - -`E0210` is the orphan rule's error for an impl of a *foreign* trait in which a type parameter is left uncovered by any local type, so the impl is not permitted in this crate. - -## What it means - -The compiler prints "type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct`)", with the note "only traits defined in the current crate can be implemented for a type parameter". The classic trigger is `impl ForeignTrait for T {}` — the parameter `T` is *uncovered*, appearing bare rather than wrapped in a local type. It enforces the **orphan rule**, the part of coherence that spans crates: a foreign trait may be implemented only when a local type is present and positioned so the impl cannot collide with one another crate might add. - -## The rule behind it - -For an impl `impl ForeignTrait for T0`, the orphan rule requires that at least one of `T0…Tn` be a local type — call the first such type `Ti` — and that no uncovered type parameter appear before `Ti`. A type is "covered" when it appears as a parameter to another type: the `T` in `Vec` is covered, a bare `T` is not. `E0210` is the specific failure where no local type covers the parameters, so a bare parameter stands in a position coherence cannot allow. Permitting it would let two unrelated crates implement the foreign trait for the parameter incompatibly, and adding a dependency could silently break a build — exactly what the rule prevents. The rule and its wording come from [RFC 2451 (re-rebalancing coherence)](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html), building on RFC 1023. Its sibling [`E0117`](e0117.md) covers the case where the types are foreign but concrete (no parameter involved). - -## Where CGP produces it - -- [Orphan-rule violation](../wiring/orphan-rule.md) — a namespace registration (`#[default_impl]` on a prefixed component or a foreign marker, or a `cgp_namespace!` re-open without `new`) that implements a foreign namespace trait for a foreign key, leaving the table parameter (`__Components__` or `__Table__`) uncovered. - -## References - -- [Error index: E0210](https://doc.rust-lang.org/error_codes/E0210.html) -- [RFC 2451 — re-rebalancing coherence](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) (the covered/uncovered ordering rule) -- [Error index: E0117](https://doc.rust-lang.org/error_codes/E0117.html) — the sibling orphan error for foreign concrete types diff --git a/docs/errors/error_codes/e0275.md b/docs/errors/error_codes/e0275.md deleted file mode 100644 index 6c07c7a0..00000000 --- a/docs/errors/error_codes/e0275.md +++ /dev/null @@ -1,23 +0,0 @@ -# E0275 — overflow evaluating the requirement - -`E0275` is what the trait solver reports when proving a bound exceeds the recursion limit without terminating — either a genuinely deep obligation or, in CGP, an infinite cycle. - -## What it means - -The compiler prints "overflow evaluating the requirement `…`", naming the bound whose evaluation recursed too far, followed by a `note:` chain of the supporting requirements and the standard `help: consider increasing the recursion limit by adding a #![recursion_limit = "…"] attribute`. The trait solver walks a requirement's supporting bounds only to a bounded depth — the [default `recursion_limit` is 128](https://doc.rust-lang.org/reference/attributes/limits.html) — and raises `E0275` when it passes that depth without resolving. - -## The rule behind it - -The recursion limit is a termination safeguard, not a correctness rule: it stops the solver from looping forever on a self-supporting bound. The `help:` to raise the limit is generic advice printed for *every* overflow, and it applies only when the obligation is merely *deep* — a finite chain that a higher limit would clear. When the requirement is a true **cycle** — a bound that supports itself, so the chain never terminates at any depth — no limit is high enough, and the fix is to break the cycle, not raise the limit. Reading an `E0275` therefore starts with deciding whether the recurring requirement in the `note:` chain is deep or circular. - -## Where CGP produces it - -Both CGP sources are true cycles, where raising the limit never helps: - -- [Wiring cycle](../wiring/wiring-cycle.md) — a component delegated to `UseContext` whose only implementation is that delegation, so the lookup chases its own tail; surfaces only when forced through a check (hidden as [`E0599`](e0599.md) when called). -- [Namespace inheritance cycle](../wiring/namespace-inheritance-cycle.md) — namespaces whose parent chain loops, caught *eagerly* at the `cgp_namespace!` definitions. - -## References - -- [Error index: E0275](https://doc.rust-lang.org/error_codes/E0275.html) -- [Rust reference — the `recursion_limit` attribute](https://doc.rust-lang.org/reference/attributes/limits.html) (default 128) diff --git a/docs/errors/error_codes/e0277.md b/docs/errors/error_codes/e0277.md deleted file mode 100644 index b3b773df..00000000 --- a/docs/errors/error_codes/e0277.md +++ /dev/null @@ -1,30 +0,0 @@ -# E0277 — trait bound not satisfied - -`E0277` is Rust's general "a required trait bound is not satisfied" error — the code most CGP dependency and lookup failures surface under once a check forces them. - -## What it means - -The compiler prints "the trait bound `X: Trait` is not satisfied", with the label "the trait `Trait` is not implemented for `X`" and often a `help:` listing types that *do* implement the trait, followed by a `required for …` chain tracing why the bound was needed. It fires whenever the trait solver must prove a bound it cannot discharge — a missing impl, an unmet `where` clause, or a supertrait obligation. - -One special case is worth recognizing because its message does not match the generic form. When the unmet bound is `Sized` on an unsized type, `rustc` reports "the size for values of type `[u8]` cannot be known at compilation time" with the note "the trait `Sized` is not implemented for `[u8]`" — still `E0277`, still an unsatisfied `Sized` bound, but phrased in terms of size rather than as `[u8]: Sized`. `Sized` is an implicit bound on ordinary generic parameters (a `T` is `Sized` unless written `?Sized`), so a [dynamically sized type](https://doc.rust-lang.org/reference/dynamically-sized-types.html) reaching such a position triggers this form. - -## The rule behind it - -`E0277` is not a single rule but the solver's report that some obligation is unsatisfiable; its meaning is entirely in *which* bound is named and *why it was required*, which the `required for …` chain records. In CGP the required-for chain almost always runs through [`IsProviderFor`](../../reference/traits/is_provider_for.md) and `CanUseComponent`, the supertraits a check uses to force the compiler to evaluate a provider's `where` clause and name the real leaf — the same bound the [hidden `E0599`](e0599.md) form suppresses. - -## Where CGP produces it - -`E0277` is the shared code of the surfaced (checked) dependency and lookup failures: - -- [Check-trait failure](../checks/check-trait-failure.md) — a check surfacing an unmet CGP-capability leaf (a `HasField`), stated in a `help:` note. -- [Unsatisfied ordinary trait bound](../checks/ordinary-trait-bound.md) — a check surfacing an unmet *ordinary* trait (`f64: Eq`) as the primary error. -- [Unregistered namespace path](../checks/unregistered-namespace-path.md) — a namespace redirect landing on a path no entry binds, reported as an unsatisfied `DefaultNamespace`/`DelegateComponent` lookup. -- [Verbose dependency cascade](../checks/verbose-cascade.md) — the same surfaced `E0277` multiplied across transitively dependent providers. -- [Higher-order provider layer failure](../checks/higher-order-provider-layer.md) — the same surfaced `E0277` where the chain shape identifies which layer of a wrapped provider carries the unmet leaf. -- [Ill-formed generated type](../lowering/ill-formed-generated-type.md) — the `Sized` special case, from a macro lowering a shorthand into an unsized type. -- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the `E0277` variant when a consumer trait is used as a `where` bound rather than called. - -## References - -- [Error index: E0277](https://doc.rust-lang.org/error_codes/E0277.html) -- [Rust reference — dynamically sized types](https://doc.rust-lang.org/reference/dynamically-sized-types.html) (the `Sized` special case) diff --git a/docs/errors/error_codes/e0428.md b/docs/errors/error_codes/e0428.md deleted file mode 100644 index 291d2c15..00000000 --- a/docs/errors/error_codes/e0428.md +++ /dev/null @@ -1,19 +0,0 @@ -# E0428 — name defined multiple times - -`E0428` is Rust's duplicate-definition error: two items define the same name in one namespace of one scope, so the compiler cannot tell which the name refers to. - -## What it means - -The compiler prints "the name `X` is defined multiple times", with "previous definition of the type `X` here" on the first definition and "`X` redefined here" on the second, closing with "`X` must be defined only once in the type namespace of this module". It fires when a struct, enum, trait, module, or other item is declared twice under the same name in the same scope. Unlike a conflicting *impl* ([`E0119`](e0119.md)), this is a collision between *definitions* — two items claiming one name, not two impls overlapping. - -## The rule behind it - -A name in a given namespace of a scope must resolve to exactly one item, so the compiler rejects a second definition of the same name rather than silently shadow or merge it. Rust keeps separate namespaces for types, values, and macros, so the message names which namespace the clash is in ("the type namespace"). The fix is to rename or remove one definition. - -## Where CGP produces it - -- [Conflicting wiring](../wiring/conflicting-wiring.md) — a generated name declared twice: a `#[cgp_component]`-derived `…Component` marker clashing with a hand-declared type, or two `#[cgp_impl(new …)]` declaring the same provider struct (which adds an [`E0119`](e0119.md) pair on the provider's own impls on top of the `E0428`). - -## References - -- [Error index: E0428](https://doc.rust-lang.org/error_codes/E0428.html) diff --git a/docs/errors/error_codes/e0576.md b/docs/errors/error_codes/e0576.md deleted file mode 100644 index 675225ea..00000000 --- a/docs/errors/error_codes/e0576.md +++ /dev/null @@ -1,20 +0,0 @@ -# E0576 — associated item not found - -`E0576` is Rust's diagnostic for naming an associated item — a type, constant, or function — that the referenced trait or type does not declare, so the qualified path resolves to nothing. - -## What it means - -The compiler prints "cannot find associated type `Name` in trait `Trait`" (or the constant/function variant), pointing at the offending path segment. It arises when a fully-qualified associated-item reference such as `::Name` names an item the trait does not define — a misspelling, a stale name after a rename, or an item that lives on a different trait. When a similarly named item exists, `rustc` adds a "similarly named associated type … defined here" note and a `help:` suggesting the correct spelling, so the fix is usually visible in the diagnostic itself. - -## The rule behind it - -Associated-item resolution is name-based: a qualified path `::Name` is resolved by looking `Name` up among `Trait`'s declared associated items, and a name with no matching declaration is a resolution error rather than a trait-solving failure. It is reported early, during name resolution and type collection, before any bound is evaluated — which is why the caret lands on the path itself rather than on a `where` clause. This is ordinary path resolution as described in the [Rust reference on paths](https://doc.rust-lang.org/reference/paths.html); there is no RFC-level subtlety, only that the item must exist to be named. - -## Where CGP produces it - -- [Unresolved imported abstract type](../lowering/unresolved-imported-type.md) — a `#[use_type]` import names an associated type the owning trait does not declare, so the macro rewrites the bare alias into a `::WrongName` path that resolves to nothing. Because the substitution preserves the identifier's span, the caret lands on the name the user wrote. - -## References - -- [Error index: E0576](https://doc.rust-lang.org/error_codes/E0576.html) -- [Rust reference — paths](https://doc.rust-lang.org/reference/paths.html) diff --git a/docs/errors/error_codes/e0599.md b/docs/errors/error_codes/e0599.md deleted file mode 100644 index b6bab0d4..00000000 --- a/docs/errors/error_codes/e0599.md +++ /dev/null @@ -1,26 +0,0 @@ -# E0599 — method exists but its trait bounds were not satisfied - -`E0599` is the method-resolution error Rust reports when a method cannot be called — and, in its "trait bounds were not satisfied" form, the one that *hides* which bound failed, which is why CGP's hidden failures surface under it. - -## What it means - -The compiler prints "the method `foo` exists for struct `X`, but its trait bounds were not satisfied", names the top-level traits it could not satisfy (`X: SomeTrait`), and then stops — it does not, in general, descend into and name the specific unmet `where`-clause bound of an inapplicable blanket or generic impl. It fires when the method probe finds a candidate method whose enabling impl does not apply to the receiver because some bound is unmet. - -## The rule behind it - -The suppression is a deliberate diagnostic heuristic, not a language rule. When a blanket impl sits among the candidate impls and one of its `where` bounds is unmet, expanding every candidate's failed sub-bound would usually be noise, so `rustc` reports only that the top-level trait is unimplemented. The cost is that the *actual* cause — the nested bound — is absent from the output, not merely buried. This is a long-standing, acknowledged limitation, tracked in [rust-lang/rust#61661](https://github.com/rust-lang/rust/issues/61661) (a closed issue whose fix improved some cases) and [#75222](https://github.com/rust-lang/rust/issues/75222) (still open). The way to recover the cause is to force the obligation to be proven *directly* — which is exactly what a CGP check does by requiring [`IsProviderFor`](../../reference/traits/is_provider_for.md), promoting the failure into a surfaced [`E0277`](e0277.md). - -In CGP this form also carries a stock misdirection: because a provider-trait method has no `self` receiver, the probe classifies it as an associated function and suggests `X::foo()`, which drops the context argument and says nothing about the missing dependency — noise to read past. - -## Where CGP produces it - -`E0599` is the shape of every *hidden* CGP failure — broken wiring reached by a consumer-trait method call rather than a check: - -- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the central case; the unmet dependency (a `HasField`, a capability, or an [ordinary trait bound](../checks/ordinary-trait-bound.md)) is suppressed. -- [Wiring cycle](../wiring/wiring-cycle.md) and [Unregistered namespace path](../checks/unregistered-namespace-path.md) — a cycle or an empty lookup, which also hide as this `E0599` when reached by a call instead of a check. - -## References - -- [Error index: E0599](https://doc.rust-lang.org/error_codes/E0599.html) -- [rust-lang/rust#61661](https://github.com/rust-lang/rust/issues/61661) — improve E0599 for unsatisfied `where`-clause bounds (closed; partial fix) -- [rust-lang/rust#75222](https://github.com/rust-lang/rust/issues/75222) — confusing notes when per-method `where` bounds are unsatisfied (open) diff --git a/docs/errors/hidden/unsatisfied-dependency.md b/docs/errors/hidden/unsatisfied-dependency.md deleted file mode 100644 index 402c6070..00000000 --- a/docs/errors/hidden/unsatisfied-dependency.md +++ /dev/null @@ -1,100 +0,0 @@ -# Unsatisfied dependency (hidden) - -A provider is wired onto a context that cannot satisfy the provider's impl-side dependency, and the failure is triggered by calling the consumer-trait method directly — so the compiler reports only that the method's trait bounds are unsatisfied (`E0599`, or `E0277` when the consumer trait is used as a bound) and hides the dependency that actually failed. - -## What triggers it - -This class arises whenever [wiring](../../reference/macros/delegate_components.md) is lazy and the mistake is exercised through the consumer trait rather than a check. A provider carries an impl-side dependency in its `where` clause; a context is wired to that provider but does not meet the dependency; and because `delegate_components!` records the entry without verifying the provider's transitive requirements, the wiring is accepted. The failure appears only when the context's consumer-trait method is finally called. The dependency's leaf can be any bound the provider names — a missing `HasField`, an unmet CGP capability like `HasName`, or an [ordinary Rust trait bound](../checks/ordinary-trait-bound.md) such as `Scalar: Eq` on an abstract type — and the hidden form suppresses it identically in every case, because the compiler's method-probe heuristic drops the nested bound without inspecting what it is. - -```rust -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasName, // impl-side dependency -{ - fn greet(&self) { - let _ = self.name(); - } -} - -#[derive(HasField)] -pub struct Person { - pub age: u8, // no `name` field, so Person does not implement HasName -} - -// Accepted even though Person cannot satisfy GreetHello's `Self: HasName`. -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} - -fn main() { - Person { age: 0 }.greet(); // fails here, at the call site -} -``` - -The same hiding occurs, in a slightly different shape, when the consumer trait is used as a bound — `fn use_it(c: &C)` called on a `Person` — rather than as a direct method call. Both exercise the broken wiring through the consumer trait, and both hide the cause; the difference is only the error code. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the recovered form, which for this class is not a reformat but a genuinely different output. This is a **hidden** class: the compiler prints a short diagnostic that names the traits it could not satisfy but never descends to the dependency that failed. Calling the method directly produces an `E0599` — "the method `greet` exists for struct `Person`, but its trait bounds were not satisfied" — whose notes state that `Person` does not satisfy `Person: CanGreet` or `Person: Greeter` and that the trait `Greeter` must be implemented, and then stop. Using the consumer trait as a `where` bound instead produces an `E0277` reporting that `Person: CanGreet` is not satisfied, again without expanding why. In neither shape does the output mention `HasName` or the missing `HasField` — the real cause is absent, not merely buried. - -The `E0599` form carries one more piece of misdirection worth naming so a reader discounts it. Alongside the unsatisfied-bound notes, the compiler adds "this is an associated function, not a method," reports `greet` under "found the following associated functions," and suggests rewriting the call as `Person::greet()`. This is an artifact of how `#[cgp_component]` lowers the method: the provider trait's `greet(context: &Context)` has no `self` receiver, so the method probe classifies it as an associated function. The suggestion is wrong — `Person::greet()` drops the required context argument — and it says nothing about the missing dependency; it is noise the class produces, not a clue. - -The reason the cause is absent is a deliberate compiler heuristic, and it is the whole point of isolating this class. `#[cgp_component]` emits a blanket impl of the consumer trait for any context that is its own provider, so when the compiler checks `Person: CanGreet` it finds that blanket impl (alongside every other candidate impl in scope), determines it does not apply because `Person: Greeter` is unmet, and declines to expand the nested `where` bound that made it inapplicable. `rustc` does not, in general, report *which* `where`-clause bound of an inapplicable blanket impl failed — it reports only that the top-level trait is unimplemented and stops — because with a blanket impl and multiple candidates present, expanding every candidate's unmet sub-bound would usually be noise. This is a long-standing, acknowledged limitation of the `E0599` diagnostic, tracked upstream in [rust-lang/rust#61661](https://github.com/rust-lang/rust/issues/61661) and [#75222](https://github.com/rust-lang/rust/issues/75222), not a quirk of CGP. The suppression is exactly what CGP's [`IsProviderFor`](../../reference/traits/is_provider_for.md) supertrait exists to defeat — but only a check that requires `IsProviderFor` *directly* forces the compiler past the heuristic; the lazy consumer-trait path never does. - -The same `E0599`/`E0277` shape, and the same absence of a cause, also arises when the wiring is not merely *unmet* but *missing* — a component never wired on the context, or an [unregistered namespace path](../checks/unregistered-namespace-path.md) whose redirect lands on an empty table slot. Whether a provider was found but its dependency failed, or no provider was found at all, the consumer-trait path hides it identically; the two are told apart only by promoting the error into a surfaced one. - -## Where the root cause is - -The root cause is **not present in the output.** A reader or tool must not scan this diagnostic for the failing dependency, because the compiler discarded it before printing. The most specific thing the output names is the unsatisfied provider trait (`Person: Greeter`) or consumer trait (`Person: CanGreet`); everything below that — the missing field, the unmet `HasName`, any deeper transitive bound — is gone. This absence is the defining property of the class and the reason it lives apart from the [surfaced classes](../checks/), whose diagnostics *do* carry the cause. - -## How cargo-cgp presents it - -`cargo-cgp` is the reason this class need not stay hidden, and it is the one class where the tool does more than reshape existing text: its next-generation trait solver **recovers the cause the raw `E0599` discarded.** Where plain `cargo check` prints a diagnostic that bottoms out at `Person: Greeter` and names no dependency, `cargo-cgp` keeps the Rust code (`E0599` at a call site, `E0277` at a `where` bound) but rewrites the headline to `[CGP-E001] the consumer trait \`CanGreet\` is not implemented for context \`Person\`` and attaches a `root cause:` note — a `[CGP-E106] missing field \`name\` on \`Person\`` leaf over a compact dependency tree that the raw output never contained at all: `[CGP-E101]` consumer-trait hop → `[CGP-E102]` provider-trait hop → `[CGP-E105]` the `HasName` capability → the `[CGP-E106]` field leaf. The method-probe misdirection — "this is an associated function, not a method" and the wrong `Person::greet()` suggestion — is dropped, so the recovered cause leads instead of the noise. - -The leaf the tree bottoms out on names the actual mistake, and it varies with the suppressed dependency's kind. A genuinely absent field is `[CGP-E106]`; a struct that never derived `HasField` coalesces to `[CGP-E108]`; a component the context never wired, or an [unregistered namespace path](../checks/unregistered-namespace-path.md), is `[CGP-E107] does not contain any delegate entry`. When the buried leaf is an [ordinary Rust trait bound](../checks/ordinary-trait-bound.md) such as `Scalar: Eq`, the tree's terminal passes the bound through in rustc's own words and the `root cause:` lead takes `[CGP-E201]` instead. In every case the recovered result matches, byte for byte, what a [check-trait failure](../checks/check-trait-failure.md) produces for the same mistake — which is exactly how `cargo-cgp` recovers it, by promoting the hidden bound to the direct one the solver will trace. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -The underlying fix is ordinary: give the context what the provider needs — add the `name` field to `Person`, or wire the getter component to an existing field — so `Person` satisfies `HasName` and `GreetHello` becomes a valid provider. The difficulty is not the fix but *finding* it, since the error points nowhere useful. - -To make the cause visible, **promote the hidden error into a surfaced one** by forcing the check at the wiring site with [`check_components!`](../../reference/macros/check_components.md): - -```rust -check_components! { - Person { - GreeterComponent, - } -} -``` - -The check asserts `CanUseComponent` for `Person`, which requires `GreetHello: IsProviderFor` as a *direct* bound. That defeats the suppression heuristic, and the compiler now reports the full `E0277` note chain ending at the missing `HasField`. That surfaced form is the [check-trait failure](../checks/check-trait-failure.md) class, and moving between the two is the standard technique the [debugging guide](../../guides/debugging.md) prescribes for a hidden dependency. - -## Notes for tooling - -The move this class needs — inspecting the fulfillment errors the trait solver produced but the diagnostic heuristic suppressed, rather than reformatting text that never held the cause — is exactly what `cargo-cgp`'s typed resolver already performs (above), which is why this class is *replaced* rather than reshaped. What remains useful to a plainer tool, or to a reader on the raw fallback, is recognizing the class without the cause: an `E0599` "method exists but its trait bounds were not satisfied" whose unmet bound is a provider trait of the form `Context: SomeProvider`, or an `E0277` on a consumer trait backed by a `…Component` blanket impl, with no note descending past that bound. A text-only post-processor cannot recover the cause here — only re-driving the solver (or promoting to a check) can. - -## Backing fixtures - -- [`acceptable/use-site/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/missing_dependency.rs) — `GreetHello` requires `Self: HasName`, `Person` lacks the `name` field, and the method is called directly; its `.rust.stderr` pins the raw `E0599` that names `Person: Greeter` (plus the method-probe advice) without descending to the missing field, and its `.cgp.stderr` the recovered `[CGP-E001]` headline over a `root cause: [CGP-E106] missing field \`name\`` tree. Its checked counterpart, [`acceptable/fields/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/missing_dependency.rs), reaches the same mistake through a check and belongs to the [check-trait failure](../checks/check-trait-failure.md) class. -- [`acceptable/use-site/ordinary_bound_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/ordinary_bound_unsatisfied.rs) — the same hidden `E0599` where the suppressed leaf is an *ordinary* trait bound (`Scalar: Eq`, with `f64` wired) rather than a `HasField`, confirming the heuristic drops the nested bound regardless of its kind; its `.cgp.stderr` recovers the cause as a `[CGP-E001]` headline over a `root cause: [CGP-E201] the trait bound \`f64: Eq\` is not satisfied` tree. Its surfaced counterpart is the [unsatisfied ordinary trait bound](../checks/ordinary-trait-bound.md) class. - -## Related - -- [Check-trait failure (surfaced)](../checks/check-trait-failure.md) — the same unmet dependency forced through a check, where the cause *is* reported; the two are the two halves of one phenomenon. -- [Unsatisfied ordinary trait bound (surfaced)](../checks/ordinary-trait-bound.md) — the surfaced class for the ordinary-trait-bound flavor of this hidden failure. -- [Debugging CGP compile errors](../../guides/debugging.md) — the playbook: read the error's shape, then move the error to the wiring site with a check to surface a hidden cause. -- [`IsProviderFor`](../../reference/traits/is_provider_for.md) and [`DelegateComponent`](../../reference/traits/delegate_component.md) — the traits every wiring error is ultimately about; `IsProviderFor` is the supertrait a check uses to defeat the suppression heuristic. -- [`check_components!`](../../reference/macros/check_components.md) and the [check-traits concept](../../concepts/check-traits.md) — why wiring is lazy and how a check forces a readable error at the wiring site. diff --git a/docs/errors/lowering/ill-formed-generated-type.md b/docs/errors/lowering/ill-formed-generated-type.md deleted file mode 100644 index dde709ce..00000000 --- a/docs/errors/lowering/ill-formed-generated-type.md +++ /dev/null @@ -1,49 +0,0 @@ -# Ill-formed generated type - -A macro accepts a field- or argument-type *shorthand* it has no dedicated rule for, lowers it literally, and the generated bound names a type that is not well-formed — most often an unsized type — so the compiler rejects it with `E0277` (the `Sized` form). - -## What triggers it - -This class is distinct from every other in the catalog: it is neither a wiring mistake nor a lazy dependency, but a macro *lowering* that produces a type the compiler cannot accept. CGP's getter and implicit-argument macros recognize a small set of field-type shorthands — `Option<&T>` for an optional field, `&[T]` for a slice field, `&str` for a `String` field — and lower each to a `HasField` bound and an accessor. Each shorthand covers a single field shape. A *combination* they provide no rule for is not rejected at macro time; it is lowered literally by whichever single rule matches first, and the resulting bound names an ill-formed type that only the compiler catches. - -The worked case is a getter typed `Option<&[T]>`, which combines the `Option<&T>` and `&[T]` shorthands: - -```rust -#[cgp_auto_getter] -pub trait HasItems { - fn items(&self) -> Option<&[u8]>; -} -``` - -The shared `parse_field_type` applies the `Option<&T>` rule, reading an `Option` field whose `T` is the slice `[u8]`, so the generated `HasField` bound names the unsized `Option<[u8]>`. Because `[u8]` is a dynamically sized type, `Option<[u8]>` is ill-formed, and the compiler rejects it. This boundary is shared by [`#[cgp_auto_getter]`](../../reference/macros/cgp_auto_getter.md), [`#[cgp_getter]`](../../reference/macros/cgp_getter.md), and a [`#[cgp_fn]`](../../reference/macros/cgp_fn.md) [`#[implicit]`](../../guides/reading-context-fields.md) argument, since all three lower field types through `parse_field_type`. CGP is working as designed: the shorthands ease the common single-shape cases, and an unsupported combination is deferred to the compiler rather than given a bespoke rule — an [acceptable failure](../../implementation/AGENTS.md), not a defect. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class, and unusually the root cause is the *primary* error, stated in plain terms. The compiler reports **`E0277`** in its dedicated `Sized` form — "the size for values of type `[u8]` cannot be known at compilation time," with the label "doesn't have a size known at compile-time" and a `help:` note "the trait `Sized` is not implemented for `[u8]`." A following `note: required by an implicit Sized bound in Option` names *where* the size is required: the `T` in `Option` carries an implicit `Sized` bound, and `[u8]` cannot meet it. The caret lands on the macro attribute (`#[cgp_auto_getter]`) because the offending type is synthesized in the expansion, and a closing note attributes the error to that attribute macro. - -Recognizing the exact message form matters, because rustc special-cases the missing-`Sized` case rather than printing a generic trait-bound line. It does **not** say "the trait bound `[u8]: Sized` is not satisfied"; it says "the size for values of type `[u8]` cannot be known at compilation time" and names `Sized` only in the `help:` note. The underlying unmet trait is still `Sized` under error code `E0277`, but the headline is the size-specific phrasing. This is the standard `rustc` diagnostic for a [dynamically sized type](https://doc.rust-lang.org/reference/dynamically-sized-types.html) reaching a position that requires a statically known size — a generic type parameter like `Option`'s `T`, which carries an implicit `Sized` bound unless written `T: ?Sized`. - -A second error usually follows and is derived noise, not a separate cause. The getter body calls `.as_ref()` on the ill-formed value, so the compiler adds an **`E0599`** — "the method `as_ref` exists for reference `&Option<[u8]>`, but its trait bounds were not satisfied" — whose notes restate `[u8]: Sized` and `Option<[u8]>: AsRef<_>` as the unmet bounds. It is a consequence of the same unsized type, downstream of the `E0277`, and points at no new mistake. - -## Where the root cause is - -The root cause is **present and sits at the top of the output**, in the primary `E0277` and its `help:` note naming `Sized` for the concrete unsized type. This is among the clearest diagnostics in the catalog: the compiler states exactly which type has no known size and where the size is required, so there is no note chain to walk and nothing suppressed. The `E0599` on `as_ref` below it is a derived consequence to skip. What the raw message does not supply is the CGP-specific reading — that the unsized type came from an *unsupported shorthand combination* the macro lowered literally, and that the fix is to change the field type rather than the wiring — because the compiler sees only the synthesized type, not the shorthand the user wrote. - -## How cargo-cgp presents it - -`cargo-cgp` does not yet rewrite this class — it passes rustc's diagnostic through unchanged. For the fixture, the tool's `.cgp.stderr` is byte-for-byte its raw `.rust.stderr`: the primary `E0277` "the size for values of type `[u8]` cannot be known at compilation time", its `Sized` `help:` note, the "required by an implicit `Sized` bound in `Option`" note, and the trailing `E0599` on `as_ref` — no `[CGP-Exxx]` code stamped, no note suppressed. The pass-through is why the fixture sits in cargo-cgp's `usability/` tier: the cause is present and stated plainly, but two things bury it for a CGP reader. The caret points at the `#[cgp_auto_getter]` attribute rather than at any type the user wrote, because the offending `Option<[u8]>` is synthesized in the expansion; and the derived `E0599` on `as_ref` adds noise below the real error. The improvement left for the tool is translation, not recovery — suppress the derived `E0599`, and read the synthesized `Option<[u8]>` back to its cause, that the field type combined the `Option<&T>` and `&[T]` shorthands into a shape CGP has no rule for. The codes cargo-cgp stamps on the classes it does rewrite are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -Change the field or argument type to a shape a single shorthand supports, so the macro lowers it to a well-formed bound. Use an owned optional container instead of an optional slice — `Option>` (returned by reference, `Option<&Vec>`, or by value) or `Option<&'static [u8]>` where a borrow of static data fits — rather than `Option<&[u8]>`, whose lowering names the unsized `Option<[u8]>`. When neither shorthand fits the shape you need, write the getter or accessor by hand: the macros only save boilerplate over a plain `HasField` impl, so a hand-written trait impl can name whatever well-formed type the field actually has. The rule to carry away is that the shorthands compose only where their single-shape lowering yields a `Sized` type; a combination that would produce a dynamically sized type is out of scope by design. - -## Backing fixtures - -- [`usability/lowering/option_slice.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/lowering/option_slice.rs) — a `#[cgp_auto_getter]` returning `Option<&[u8]>`, lowered to a `HasField` bound over the unsized `Option<[u8]>`; its `.rust.stderr` pins the primary `E0277` "the size for values of type `[u8]` cannot be known at compilation time" with the `Sized` `help:` note, the "required by an implicit `Sized` bound in `Option`" note, and the derived `E0599` on `as_ref`. Its `.cgp.stderr` is identical — the pass-through that places the fixture in the `usability/` tier, since cargo-cgp does not yet suppress the derived `E0599` or read the synthesized type back to the shorthand combination. - -## Related - -- [`#[cgp_auto_getter]`](../../reference/macros/cgp_auto_getter.md), [`#[cgp_getter]`](../../reference/macros/cgp_getter.md), [`#[cgp_fn]`](../../reference/macros/cgp_fn.md), and [reading context fields](../../guides/reading-context-fields.md) — the macros whose shared `parse_field_type` lowers the shorthands, and the field-type forms they support. -- [`cgp_auto_getter` implementation document](../../implementation/entrypoints/cgp_auto_getter.md) — the Behavior-and-corner-cases and Failure-modes account of why the unsupported combination is deferred to the compiler. -- [`HasField`](../../reference/traits/has_field.md) — the trait the generated bound targets, whose value type must be `Sized` here. -- [`E0277`](../error_codes/e0277.md) and [dynamically sized types](https://doc.rust-lang.org/reference/dynamically-sized-types.html) — the error-code reference (including the `Sized` special case) and the Rust reference for the size requirement the generated type violates. diff --git a/docs/errors/lowering/unresolved-imported-type.md b/docs/errors/lowering/unresolved-imported-type.md deleted file mode 100644 index 6ad995c4..00000000 --- a/docs/errors/lowering/unresolved-imported-type.md +++ /dev/null @@ -1,58 +0,0 @@ -# Unresolved imported abstract type - -A `#[use_type]` import names an associated type the owning trait does not declare, so the macro rewrites the bare alias into a `::WrongName` path that resolves to nothing and the compiler rejects it with `E0576`. - -## What triggers it - -This class is a *lowering* failure, not a wiring one: the mistake is a misnamed associated type in the `#[use_type]` attribute itself, which the macro cannot validate at expansion time. `#[use_type]` works by textual substitution — it rewrites every bare use of an imported alias into `::Assoc` — and it has no knowledge of which associated types the trait actually declares. So an alias that names a nonexistent associated type is lowered faithfully into a qualified path, and only the compiler, resolving that path, discovers the name is not there. - -The canonical case is a typo in the imported type name: - -```rust -#[cgp_type] -pub trait HasScalarType { - type Scalar; -} - -#[cgp_fn] -#[use_type(HasScalarType.Scalr)] // typo: the trait declares `Scalar`, not `Scalr` -pub fn get_scalar(&self) -> Scalr { - todo!() -} -``` - -The import declares the alias `Scalr`, so the bare `Scalr` in the return type is rewritten to `::Scalr`, which names an associated type `HasScalarType` never declares. The same failure arises from a stale name after a trait rename, or from importing an item that lives on a different trait than the one named. CGP is working as designed here: it cannot see the trait's item list during expansion, so it lowers the name literally and defers to the compiler — an [acceptable failure](../../implementation/AGENTS.md), not a defect. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class with the simplest diagnostic in the catalog: a single [`E0576`](../error_codes/e0576.md), "cannot find associated type `Scalr` in trait `HasScalarType`", with the caret on the offending name. Because the substitution copies the *span* of the identifier the user wrote onto the rewritten path, the caret lands on the `Scalr` in the user's signature — the token they actually typed — rather than on the `#[use_type]` attribute or the whole macro block. When a similarly named item exists, `rustc` adds a "similarly named associated type `Scalar` defined here" note pointing at the trait's real declaration and a `help:` that suggests the correct spelling inline. There is no note chain, no CGP scaffolding, and no `IsProviderFor`/`DelegateComponent` frame, because the failure is caught during name resolution, before any bound is evaluated. - -## Where the root cause is - -The root cause is **present and is the entire diagnostic** — the primary `E0576` names both the missing associated type and the trait it was sought in, and the caret points at the exact token to change. This is the opposite of the hidden and cascading classes: nothing is suppressed, nothing is buried, and there is no verbosity to wade through. The `help:` note, when present, even supplies the fix. The only CGP-specific fact the message does not state is that the name came from a `#[use_type]` import — but the caret sits on the alias in the signature, which is the same name written in the attribute, so following it to the attribute is immediate. - -## How cargo-cgp presents it - -`cargo-cgp` does not rewrite this class — it passes rustc's diagnostic through unchanged, and for the misnamed-type case it does not need to. For the `use_type_unknown_assoc` fixture the tool's `.cgp.stderr` is byte-for-byte its raw `.rust.stderr`: the `E0576`, the "similarly named associated type `Scalar` defined here" note, and the `help:` suggesting the correct spelling — no `[CGP-Exxx]` code stamped, nothing suppressed. That fixture sits in cargo-cgp's `acceptable/` tier precisely because the raw diagnostic already leads with the cause, with the caret on the user's own token and the fix in the `help:`; passing it through is the right outcome, not a gap. - -The [unresolved-context sibling](#a-sibling-the-unresolved-context) below is the same pass-through with a different verdict. Its two `E0425` "cannot find type" errors are copied through unchanged too, but its fixture sits in `usability/` rather than `acceptable/`: the caret lands on the bare alias inside the `#[use_type(HasA.A in B, HasB.B in A)]` attribute, and the raw message gives no hint that a `use_type` grounding *cycle* is why the alias never resolved — the CGP-level reading the tool could add but does not yet. The codes cargo-cgp stamps on the classes it does rewrite are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -Correct the imported name so it matches an associated type the trait declares — change `#[use_type(HasScalarType.Scalr)]` and every use of the alias to `Scalar`, or use an `as` clause (`#[use_type(HasScalarType.{Scalar as Scalr})]`) if the short local name was intended. The `help:` note usually names the correct spelling outright. If the type genuinely lives on a different trait, point the import at that trait instead. Because the diagnostic is a plain resolution error at the user's own token, no CGP-specific tracing is needed. - -## A sibling: the unresolved *context* - -The same textual-substitution mechanism produces a related failure when the part that cannot be resolved is the `in Context`, not the associated-type name. Two nested imports whose `in Context` clauses reference each other — `#[use_type(HasA.A in B, HasB.B in A)]` — form a cycle with no valid grounding order. Grounding iterates to a fixpoint and deliberately stops rather than loops, so the context aliases are never resolved and the rewrite leaves the bare `A` and `B` in type position. The compiler reports this as [`E0425`](https://doc.rust-lang.org/error_codes/E0425.html) "cannot find type" — a *type*, not an associated type, so a different code from the misnamed-name case above — with the caret on the unresolved alias the user wrote in the attribute. The fix is the same in spirit: make the imports form an acyclic chain (any acyclic order grounds fine). CGP could detect the cycle locally and reject it at macro time, but currently lowers it faithfully and defers to the compiler. - -## Backing fixtures - -- [`acceptable/lowering/use_type_unknown_assoc.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/lowering/use_type_unknown_assoc.rs) — a `#[cgp_fn]` importing `HasScalarType.Scalr` where the trait declares `Scalar`; its `.rust.stderr` pins the `E0576` with the caret on the `Scalr` in the signature and the "similarly named associated type" `help:`, doubling as a guard that the substitution preserves the user's identifier span, and its `.cgp.stderr` is identical — the pass-through that keeps the fixture in the `acceptable/` tier, since the raw error already leads with the cause. -- [`usability/lowering/use_type_cyclic_context.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/lowering/use_type_cyclic_context.rs) — the unresolved-*context* sibling: two `in Context` clauses referencing each other, whose `.rust.stderr` pins two `E0425` "cannot find type" errors with the carets on the unresolved `in` aliases in the attribute. Its `.cgp.stderr` is identical too, but the fixture sits in `usability/` — the pass-through does not reveal that a `use_type` grounding cycle is the cause. - -## Related - -- [`#[use_type]`](../../reference/attributes/use_type.md) and [`#[cgp_type]`](../../reference/macros/cgp_type.md) — the import attribute whose textual substitution produces the unresolved path, and the macro defining the abstract-type trait it imports from. -- [Ill-formed generated type](ill-formed-generated-type.md) — the sibling lowering class, where the generated type is *resolvable* but not well-formed (an unsized type) rather than unresolvable by name. -- [`E0576`](../error_codes/e0576.md) — the Rust error code this class is reported under. -- [Debugging CGP compile errors](../../guides/debugging.md) — the prescriptive playbook this catalog supports. diff --git a/docs/errors/wiring/conflicting-wiring.md b/docs/errors/wiring/conflicting-wiring.md deleted file mode 100644 index 9b0cb6b2..00000000 --- a/docs/errors/wiring/conflicting-wiring.md +++ /dev/null @@ -1,81 +0,0 @@ -# Conflicting wiring - -The same component key or generated name is wired or declared twice, so the expansion emits two overlapping impls and the compiler rejects them with a coherence error (`E0119`) or a duplicate-definition error (`E0428`). - -## What triggers it - -CGP lowers each wiring block independently, with no view of any other block or of the surrounding module, so a collision that only a whole-program view could catch is left to the compiler. The mistake takes several forms, all reducing to "one key or name defined twice": - -```rust -// Duplicate key — the same component mapped twice (E0119). -delegate_components! { Person { GreeterComponent: GreetHello } } -delegate_components! { Person { GreeterComponent: GreetGoodbye } } - -// Overlapping generic — a generic table and a specific one collide (E0119). -delegate_components! { Wrapper { GreeterComponent: GreetHello } } -delegate_components! { Wrapper { GreeterComponent: GreetHello } } - -// Duplicate generated name — struct declared twice (E0428, plus E0119 on its impls). -#[cgp_impl(new GreetHello)] impl Greeter { /* … */ } -#[cgp_impl(new GreetHello)] impl Greeter { /* … */ } -``` - -The same shape appears with an `open` header colliding with an explicit mapping, a `@`-path duplicated under a `namespace` header, a duplicate `cgp_namespace!` entry, a `#[cgp_component]`-derived marker clashing with a hand-declared type, a duplicate `#[default_impl]` key, or a duplicate `check_components!` entry. - -Two namespace collisions that also produce `E0119` are documented separately, because their mistake is not a duplicate declaration but a namespace usage: two blanket forwardings that overlap (joining two namespaces, or a namespace join plus a bare-key `for` loop) are the [overlapping namespace forwarding](namespace-forwarding-conflict.md) class, and a specific entry that overrides a key a namespace already claims (a context re-wiring a registered path, or a child namespace redefining an inherited entry) is the [namespace override conflict](namespace-override-conflict.md) class. This document covers the plain duplicate-or-overlapping *declaration*. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. There are two error codes, by whether the collision is between *impls* or between *definitions*. A duplicate key or overlapping generic produces **[`E0119`](../error_codes/e0119.md) conflicting implementations**; a duplicate generated *name* produces **[`E0428`](../error_codes/e0428.md) "the name … is defined multiple times"**. Both point precisely: `E0119` carries two carets — "first implementation here" on the earlier entry and "conflicting implementation for ``" on the later one, aimed at the offending keys rather than the whole block — and `E0428` carries "previous definition here" / "redefined here". - -How many `E0119`s one duplicate produces depends on how many impls the entry generates, and knowing this lets a reader treat the pair as one logical conflict rather than two mistakes. A **context-wiring** entry — a `delegate_components!` mapping, or a `namespace`/`open`/`for` header — generates *both* a `DelegateComponent` table impl and an [`IsProviderFor`](../../reference/traits/is_provider_for.md) forwarding impl (so dependency errors stay diagnosable through checks), so a duplicate yields a **pair** of `E0119`s at the same caret, one for each trait. An entry that generates only a single lookup impl yields a **single** `E0119`: a duplicate [`#[default_impl]`](../../reference/traits/default_namespace.md) conflicts on its one `DefaultImpls…` impl, and a duplicate [`cgp_namespace!`](../../reference/macros/cgp_namespace.md) body entry (a `:` mapping or a `=>` redirect) conflicts on its one namespace-trait impl. A duplicated provider *name* is the compound case: `E0428` on the struct, plus the `E0119` pair on the provider's own provider-trait and `IsProviderFor` impls. - -Two wrinkles round out the shape, and the second is where Rust's coherence reasoning shows through. When the key is a `@`-path, the conflicting trait's name expands into a long `PathCons>` type that dominates the message — the caret still lands on the path leaf, so read the caret, not the type. And a collision in which one impl is a *blanket* — such as an `open` header, which lowers to `impl for Ctx` over every key, colliding with a specific mapping — often carries an extra "downstream crates may implement …" note. That note is `rustc` making its coherence rule explicit, not a second, separate problem. Coherence forbids two impls that *could* both apply to some type, and it reasons about types a downstream crate might add, not only the types in scope; because the blanket's applicability to a given key hinges on a trait bound a downstream crate could later satisfy, the compiler cannot prove the two impls will never overlap, so it rejects them and cites the hypothetical future impl as the reason. This future-compatibility (negative-reasoning) rule is what [RFC 2451](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) formalizes. The two namespace classes split off above turn on the same reasoning: the [override conflict](namespace-override-conflict.md) carries this note where a specific entry meets a namespace's blanket forwarding, while the [forwarding conflict](namespace-forwarding-conflict.md) is a pure blanket-versus-blanket overlap that prints fully-generic `DelegateComponent<_>` / `IsProviderFor<_, _, _>` carets with no downstream note. - -## Where the root cause is - -The root cause is **present and precise**: the two carets name the two conflicting entries directly, and the error code names the kind of collision. This is a *structural* class, not a hidden or cascading one, so there is no note chain to walk and no suppressed cause to recover — the diagnostic points at exactly the two lines to reconcile. The only reading skill it demands is ignoring the expanded `PathCons<…>` type on a path-key conflict and trusting the caret. - -## How cargo-cgp presents it - -`cargo-cgp` keeps the `E0119` code but rewrites the headline to name the *kind* of collision and de-duplicates the pair into one message. Because this is a structural class, there is no `root cause:` tree: both `rustc` carets are preserved (they are already the answer), the redundant `IsProviderFor` half of a generated pair is suppressed so one duplicate reads as one conflict, and any `@`-path key is resugared to bare `@a.b.*` notation instead of the `PathCons>` spine. Which `[CGP-E00x]` code it stamps distinguishes the four shapes this class produces: - -- A duplicate key or overlapping generic becomes **`[CGP-E004]` duplicate wiring** — `` [CGP-E004] duplicate wiring for component `GreeterComponent` on `Person` `` (or, for a `@`-path, `` duplicate wiring for `@cgp.core.error.ErrorTypeProviderComponent.*` on `App` ``). -- An `open` header colliding with an explicit mapping becomes **`[CGP-E007]` redirect collision** — `` [CGP-E007] component `GreeterComponent` on `Person` is redirected to `@GreeterComponent` `` — carrying a `help:` that names the redirected key to wire the provider under (`` wire the provider `GreetHello` with the key `@GreeterComponent` ``). -- The same key redirected twice — two `=>` mappings on a context, or one `@`-path registered twice inside a `cgp_namespace!` block — becomes **`[CGP-E008]` duplicate redirect**, naming both targets when they differ (`` duplicate redirect for component `FooComponent` on `App`: redirected to both `@app.foo` and `@app.bar` ``). - -Two forms in this class are **pass-throughs** that `cargo-cgp` does not rewrite, so a reader sees the raw diagnostic above. The `E0428` name clashes (`duplicate_component_name`, `duplicate_provider_name`) keep `rustc`'s uncoded `E0428` — for a duplicate provider, the surviving `E0119` on the provider trait is kept too, with its `IsProviderFor` half suppressed — because `E0428` already points precisely at the two definitions. A duplicate `#[default_impl]` key is likewise uncoded: its lone `E0119` on the `DefaultImpls…` impl passes through, a small gap where the `[CGP-E004]` rewrite does not yet reach. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -Remove one of the two entries. For a duplicate check-trait name from two tables over one context, add a `#[check_trait(Name)]` to one. The namespace collisions split off above have their own remedies — inheriting rather than joining several namespaces, or overriding only a path the namespace leaves unbound — covered in [overlapping namespace forwarding](namespace-forwarding-conflict.md) and [namespace override conflict](namespace-override-conflict.md). - -## Notes for tooling - -`cargo-cgp` already does the collapsing and resugaring this class needs (above); what remains is the pass-through set — the `E0428` name clashes and the duplicate `#[default_impl]` key, all kept uncoded. Those are already precise enough to relay verbatim, but folding the `default_impl` `E0119` into `[CGP-E004]` would make the family complete. - -## Backing fixtures - -The `.rust.stderr` snapshot pins the raw `E0119`/`E0428` shape and the `.cgp.stderr` the reshaped headline. The `E0119` conflicts `cargo-cgp` rewrites: - -- [`wiring/duplicate-keys/duplicate_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_key.rs) and [`wiring/duplicate-keys/duplicate_key_same_block.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_key_same_block.rs) — the same key mapped twice, across two blocks and within one; the `.rust.stderr` pins the `DelegateComponent` + `IsProviderFor` pair of carets, the `.cgp.stderr` the single `[CGP-E004]` headline. -- [`wiring/duplicate-keys/overlapping_generic.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/overlapping_generic.rs) — a generic ` Wrapper` table overlapping a specific `Wrapper` table, also `[CGP-E004]`. -- [`wiring/duplicate-keys/duplicate_open_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_open_key.rs) — an `open` header colliding with an explicit mapping; the `.rust.stderr` carries the "downstream crates may implement" note, the `.cgp.stderr` the `[CGP-E007]` redirect-collision headline with its `help:` naming the `@GreeterComponent` key. -- [`wiring/duplicate-keys/duplicate_redirect.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_redirect.rs) — the same component redirected to two paths on one context, rewritten to `[CGP-E008]` naming both targets. -- [`wiring/namespace-paths/delegate_duplicate_path_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/delegate_duplicate_path_key.rs) — a duplicated `@`-path key whose raw conflicting trait name is a long `PathCons>` type; the `.cgp.stderr` resugars it to the bare `@cgp.core.error.ErrorTypeProviderComponent.*` path under `[CGP-E004]`. -- [`wiring/namespace-paths/namespace_duplicate_path_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/namespace_duplicate_path_key.rs) — the same `@`-path redirected twice inside one `cgp_namespace!` block, a single `E0119` on the namespace trait rewritten to `[CGP-E008]`. - -The two namespace collisions that are *not* duplicate declarations live with their own classes: `override_registered_path.rs` and `inherited_override_conflict.rs` under [namespace override conflict](namespace-override-conflict.md), and `for_loop_bare_key.rs` and `two_namespaces_joined.rs` under [overlapping namespace forwarding](namespace-forwarding-conflict.md). - -The pass-through conflicts `cargo-cgp` leaves uncoded: - -- [`wiring/namespace-paths/duplicate_default_impl.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/duplicate_default_impl.rs) — two `#[default_impl]` registering the same key, a *single* `E0119` on the one `DefaultImpls1` impl; the `.cgp.stderr` matches the `.rust.stderr`, the gap noted above. -- [`wiring/duplicate-keys/duplicate_component_name.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_component_name.rs) — a derived `…Component` marker clashing with a hand-declared type; `E0428` kept verbatim. -- [`wiring/duplicate-keys/duplicate_provider_name.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_provider_name.rs) — two `#[cgp_impl(new …)]` declaring the same provider struct; the `.cgp.stderr` keeps the `E0428` plus the surviving `E0119` on the provider trait `Greeter<_>` (its `IsProviderFor` half suppressed), all uncoded. - -## Related - -- [Orphan-rule violation](orphan-rule.md), [Wiring cycle](wiring-cycle.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. -- [Overlapping namespace forwarding](namespace-forwarding-conflict.md) and [Namespace override conflict](namespace-override-conflict.md) — the two namespace `E0119` collisions split off from this class, by usage rather than by error code. -- [`delegate_components!`](../../reference/macros/delegate_components.md), [`cgp_namespace!`](../../reference/macros/cgp_namespace.md), and [`DelegateComponent`](../../reference/traits/delegate_component.md). -- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0119`/`E0428` entries in the decoder. diff --git a/docs/errors/wiring/namespace-forwarding-conflict.md b/docs/errors/wiring/namespace-forwarding-conflict.md deleted file mode 100644 index 4e7f1f99..00000000 --- a/docs/errors/wiring/namespace-forwarding-conflict.md +++ /dev/null @@ -1,71 +0,0 @@ -# Overlapping namespace forwarding - -A context's wiring emits two *blanket* forwarding impls that each cover every component key — by joining two namespaces, or by joining one namespace alongside a bare-key `for` loop — and the compiler rejects the overlap with `E0119`. - -## What triggers it - -This class arises when a single [`delegate_components!`](../../reference/macros/delegate_components.md) block produces more than one blanket [`DelegateComponent`](../../reference/traits/delegate_component.md) impl over the *whole* key space. A `namespace N;` header forwards every unresolved key through the namespace `N`, so it lowers to a blanket `impl DelegateComponent for Ctx where Key: N` (paired with the matching [`IsProviderFor`](../../reference/traits/is_provider_for.md) forwarding). Two such headers — or one header plus a bare-key `for` loop, which lowers to the same all-keys shape — produce two blanket impls that overlap, because a key could satisfy both `where` clauses at once. - -The first form is **joining two namespaces on one context**: - -```rust -delegate_components! { - App { - namespace NamespaceA; - namespace NamespaceB; // second blanket DelegateComponent impl — overlaps the first - } -} -``` - -The second form is **a bare-key `for` loop beside a namespace join**, where the loop wires `Key` directly rather than embedding it in a path: - -```rust -delegate_components! { - App { - namespace DefaultNamespace; - - for in GreeterTable { - Key: Value, // blanket DelegateComponent impl — overlaps the namespace's - } - } -} -``` - -Both reduce to the same mistake: two impls that each claim every key. A context can forward through at most one namespace, and a `for` loop's key must sit inside a path (`@app.SomeComponent.Key: Value`) so it keys a *concrete* path rather than every key. CGP lowers each blanket impl faithfully and cannot see from one block that two of them span the same keys, so it defers the overlap to the compiler. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **structural** class reported as a pair of **[`E0119`](../error_codes/e0119.md) conflicting implementations**, one for each trait a namespace join forwards — the `DelegateComponent<_>` table impl and the `IsProviderFor<_, _, _>` forwarding impl that keeps dependency errors diagnosable through [checks](../../reference/macros/check_components.md). Both are printed with fully-generic `DelegateComponent<_>` / `IsProviderFor<_, _, _>` types, and each carries the two-caret shape: "first implementation here" on the first `namespace` header, and "conflicting implementation for `App`" on the second header (or on the loop's `Key: Value` line). The carets land on the entries the user wrote, so the diagnostic points straight at the two lines to reconcile. - -The signature of this class is that **both** conflicting types are *fully generic* — `DelegateComponent<_>` and `IsProviderFor<_, _, _>`, with no concrete key — and the conflict carries **no** "downstream crates may implement …" note. That is what separates it from the [namespace override conflict](namespace-override-conflict.md), where one side of the overlap names a *specific* key (a path or a component marker). The generic-versus-generic shape follows from how Rust's coherence checker reasons here. Coherence forbids two impls that could both apply to one type, and both impls have the form `impl DelegateComponent for App where Key: SomeNamespace`: the self type is the same local `App`, and the key is a free parameter bounded only by a namespace trait. Nothing prevents a single key type from implementing both namespace traits, so the compiler proves the overlap *directly*, from the impls in front of it — it needs no hypothetical future impl to construct the conflict, which is why no downstream note appears. The [conflicting wiring](conflicting-wiring.md) class carries the full account of `E0119` and the RFC 2451 coherence reasoning this builds on; the point here is only that a note-free `E0119` on generic `DelegateComponent<_>`/`IsProviderFor<_, _, _>` is this class's signature. - -## Where the root cause is - -The root cause is **present and precise**: the two carets name the two overlapping entries directly, and the fully-generic trait types confirm the overlap spans every key. This is a structural class with no note chain to walk and nothing suppressed, so a reader trusts the carets and reconciles the two lines. The only reading the diagnostic does not supply is the CGP-specific fix — that a context forwards through one namespace, not several — because the message describes the collision in impl terms rather than in terms of the wiring intent. - -## How cargo-cgp presents it - -`cargo-cgp` recognizes this class and rewrites it to state the intent the raw impl-level collision hides. It keeps the `E0119` code, collapses the `DelegateComponent<_>` + `IsProviderFor<_, _, _>` pair into one message (suppressing the redundant `IsProviderFor` half), and stamps **`[CGP-E006]` multiple namespaces** — `` [CGP-E006] only one namespace can be used for each target type in `delegate_components!`, but `App` uses both `NamespaceA` and `NamespaceB` ``. The two `rustc` carets are kept, landing on the two `namespace` headers (or, for the loop form, on the second header and the loop's `Key: Value` line), because they already point at the lines to reconcile; there is no `root cause:` tree, this being a structural class. The bare-key `for` loop form produces the *same* `[CGP-E006]` headline, naming the loop's table (`GreeterTable`) as the second "namespace", which is exactly right — a bare-key loop forwards over every key just as a namespace join does. The code is defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -Emit only one blanket forwarding impl. When two namespaces are joined, keep a single `namespace N;` and fold the other namespace's entries into `N` through *inheritance* — define one namespace that inherits the rest (`cgp_namespace! { new Combined: NamespaceA { … } }`, itself inheriting further) and join that one, per the [namespaces guide](../../guides/namespaces-and-prefixes.md). When a bare-key `for` loop collides with a namespace join, embed the loop's key in a path (`@app.SomeComponent.Key: Value`) so it keys a concrete dispatch path instead of every key — which is also why a `for` loop is the natural tool for a generic-parameter component, whose dispatch path *is* the loop key. Either fix leaves the context with one forwarding impl and the overlap gone. - -## Notes for tooling - -`cargo-cgp` already recognizes and rewrites this class in full (above), leading with the `[CGP-E006]` wiring-intent headline rather than the impl-level collision. The one signal it keys on is worth restating for any post-processor: the absent "downstream crates may implement …" note over fully-generic `DelegateComponent<_>`/`IsProviderFor<_, _, _>` types is what distinguishes this class from the [namespace override conflict](namespace-override-conflict.md), and so routes the collision to `[CGP-E006]` rather than the override's `[CGP-E005]`. - -## Backing fixtures - -The `.rust.stderr` snapshot pins the raw generic `E0119` pair and the `.cgp.stderr` the `[CGP-E006]` reshaping. - -- [`wiring/namespace-paths/two_namespaces_joined.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/two_namespaces_joined.rs) — two `namespace` joins on one context; the `.rust.stderr` pins the `E0119` pair with carets on the two `namespace` lines and no downstream note, the `.cgp.stderr` the single `[CGP-E006]` headline naming both `NamespaceA` and `NamespaceB`. -- [`wiring/duplicate-keys/for_loop_bare_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/for_loop_bare_key.rs) — a bare-key `for` loop beside a namespace join; the `.rust.stderr` pins the same generic `E0119` pair with the conflicting caret on the loop's `Key: Value` line, the `.cgp.stderr` the `[CGP-E006]` headline naming `DefaultNamespace` and the loop's `GreeterTable`. - -## Related - -- [Namespace override conflict](namespace-override-conflict.md) — the sibling namespace `E0119`, a *specific*-versus-blanket overlap where one side names a concrete key (a path or a component marker); contrast it by the generic-versus-generic types this class prints. -- [Conflicting wiring](conflicting-wiring.md) — the general `E0119`/`E0428` class for a key or name declared twice, and the full account of Rust's coherence reasoning ([RFC 2451](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html)) that this class specializes. -- [Orphan-rule violation](orphan-rule.md), [Wiring cycle](wiring-cycle.md), [Namespace inheritance cycle](namespace-inheritance-cycle.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. -- [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md), [`DefaultNamespace`](../../reference/traits/default_namespace.md), and the [namespaces guide](../../guides/namespaces-and-prefixes.md) — the `namespace`/`for` statements whose blanket impls overlap. -- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0119` entry in the decoder. diff --git a/docs/errors/wiring/namespace-inheritance-cycle.md b/docs/errors/wiring/namespace-inheritance-cycle.md deleted file mode 100644 index f91e69c3..00000000 --- a/docs/errors/wiring/namespace-inheritance-cycle.md +++ /dev/null @@ -1,52 +0,0 @@ -# Namespace inheritance cycle - -Two namespaces that inherit from each other — or one that inherits itself — make resolving any key chase the inheritance chain forever, so the trait solver overflows with `E0275`, reported *at the `cgp_namespace!` definitions themselves*. - -## What triggers it - -The mistake is a circular parent chain among namespaces: `A` names `B` as its parent while `B` names `A`, or a namespace names itself. A [namespace](../../reference/macros/cgp_namespace.md) inherits its parent through a blanket impl whose `where` clause requires the parent's lookup trait, so a parent chain that loops back on itself produces a `where` clause that can never be discharged. - -```rust -cgp_namespace! { - new NamespaceA: NamespaceB {} // A inherits B -} - -cgp_namespace! { - new NamespaceB: NamespaceA {} // B inherits A — the chain loops -} -``` - -`new NamespaceA: NamespaceB` emits the inheritance blanket impl `impl NamespaceA for Key where Key: NamespaceB<…>, Key: NamespaceB`, and `new NamespaceB: NamespaceA` emits the mirror. A self-inheriting `new A: A {}` collapses the two into one impl whose bound requires the trait it defines. CGP cannot see the parent chain is circular from one macro invocation — each `cgp_namespace!` knows only its own parent — so it lowers each namespace faithfully and defers the contradiction to the compiler. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — and for this one class it is the *only* place the error appears, because [`cargo-cgp` does not report it at all](#how-cargo-cgp-presents-it). The compiler reports **`E0275` "overflow evaluating the requirement"**, and the defining property of this class is *where* it lands: on the `cgp_namespace!` blocks that define the namespaces, with no context or use site required. Evaluating the well-formedness of `NamespaceA`'s inheritance impl means evaluating its `where` bound `__Key__: NamespaceB<…>`, which pulls in `NamespaceB`'s impl and its bound `__Key__: NamespaceA<…>`, which pulls in the first again — an infinite chain. The overflow message names the requirement that recurses (`__Key__: NamespaceA<__NamespaceBComponents>`), a `note:` chain that walks `NamespaceB<…>` → `NamespaceA<…>` and names the loop, a `= note: N redundant requirements hidden` marking the collapsed repetition, and the standard `help: consider increasing the recursion limit`. Each of the two definitions carries its own `E0275`. - -This eager, definition-site failure is the sharp contrast with the sibling [wiring cycle](wiring-cycle.md). A [`UseContext`](../../reference/providers/use_context.md) delegation cycle is *lazy*: the wiring is accepted, and the overflow appears only when the wiring is forced through a check (and hides as an [`E0599`](../hidden/unsatisfied-dependency.md) when reached by a plain call). A namespace inheritance cycle is *eager*: the cycle lives in the `where` clause of a generated blanket impl, and the compiler evaluates that clause when it checks the impl, so the overflow fires at the definition before anything uses the namespace. A context that *does* join the cycle and is then checked simply adds more `E0275` blocks — an `App: DelegateComponent` overflow at the check — on top of the definition-site ones; it does not change the cause. - -The overflow itself is ordinary `rustc` behavior. The trait solver walks a requirement's supporting bounds to a bounded depth — the [default `recursion_limit` is 128](https://doc.rust-lang.org/reference/attributes/limits.html) — and reports [`E0275`](../error_codes/e0275.md) when it exceeds that depth without terminating. A genuine cycle never terminates, so the limit is only ever reached, never cleared; the `help:` suggestion to raise it is generic advice the compiler prints for every overflow and does not apply here. - -## Where the root cause is - -The cause is **present and the diagnostic points straight at it**: the two `E0275` carets land on the two `cgp_namespace!` definitions, and the `note:` chain names both namespaces and the loop between them. Unlike a hidden failure there is nothing to promote and nothing suppressed — the participants are all named. What the diagnostic does not state is the remedy, and it actively misleads on it: the `help: increase the recursion limit` line suggests a fix that cannot work, because the requirement does not terminate at any depth. Read the overflow as "these namespaces' parent chain is circular," not as "the chain is merely deep." - -## How cargo-cgp presents it - -`cargo-cgp` does not report this error at all — under its next-generation trait solver the mutually-inheriting namespaces **compile clean**, with no `E0275` to reshape. This is the one class in the catalog where the tool's output is *empty* rather than a rewrite of the raw diagnostic. It is a next-solver divergence, but the reverse of the usual one: where the caveat about the next solver normally warns that it may *report* something the stable solver does not, here it *omits* an error the stable solver raises — a missing error, not an added one. The next-gen solver's cycle handling terminates the inheritance chain instead of overflowing on it, so the contradiction the current solver treats as a fatal overflow simply resolves. - -The practical consequence is that this class surfaces only for a reader on plain `rustc`/`cargo check`; the recommended `cargo-cgp` toolchain will not flag the circular inheritance, and there is no `.cgp.stderr` to consult. That does not make the wiring correct — a self- or mutually-inheriting namespace is still a mistake — only invisible under the tool, which is why the fix in [Resolving it](#resolving-it) still applies even when nothing complains. - -## Resolving it - -Break the cycle so the parent chain is acyclic — a namespace's inheritance must form a tree, not a loop. Remove one direction of the mutual inheritance so one namespace is unambiguously the parent, or, when both namespaces genuinely need a shared set of entries, factor those entries into a third base namespace that both inherit from, rather than having them inherit each other. Raising `#![recursion_limit]` is the wrong move: the chain has no terminating step, so no limit is high enough. - -## Backing fixtures - -This is the **one class in the catalog with no `cargo-cgp` fixture**, because there is no error to snapshot: under `cargo-cgp`'s next-generation solver the mutually-inheriting namespaces compile clean, so a fixture would carry an empty `.cgp.stderr` and misrepresent the class as fixed. The absence is deliberate and is recorded on the `cargo-cgp` side as the single un-migrated class, in its [UI-test README](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md) — which explains it as a *missing* error (the next-solver divergence above) rather than a suppressed cause, and notes it will be reproduced only if a genuinely reproducible case is found. The raw `E0275` described above therefore has no blessed `.rust.stderr` either; it is what plain `cargo check` prints today, verified against the compiler rather than against a committed snapshot. - -## Related - -- [Wiring cycle](wiring-cycle.md) — the sibling cycle class through [`UseContext`](../../reference/providers/use_context.md); both overflow with `E0275`, but that one is lazy (surfaces only when checked, hides as `E0599` when called) while this one is eager (caught at the namespace definitions). -- [Conflicting wiring](conflicting-wiring.md), [Orphan-rule violation](orphan-rule.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. -- [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md) and [`DefaultNamespace`](../../reference/traits/default_namespace.md) — the inheritance blanket impl whose `where` clause the cycle poisons. -- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0275`/overflow entry in the decoder. diff --git a/docs/errors/wiring/namespace-override-conflict.md b/docs/errors/wiring/namespace-override-conflict.md deleted file mode 100644 index b7e12bbf..00000000 --- a/docs/errors/wiring/namespace-override-conflict.md +++ /dev/null @@ -1,86 +0,0 @@ -# Namespace override conflict - -Wiring tries to *override* a key or path that a namespace already claims — a context re-wiring a path its joined namespace registers, or a child namespace redefining an entry it inherits — and the specific entry collides with the namespace's blanket impl, so the compiler rejects the overlap with `E0119`. - -## What triggers it - -This class is the failure of a natural-seeming intent: "the namespace sets this, but I want something different here." A namespace supplies entries as *defaults*, and the inheritance-with-override pattern lets a context shadow one — but only for a key the namespace *routes* to without *terminating*. A key the namespace itself binds (through a `:` body entry, a [`#[default_impl]`](../../reference/traits/default_namespace.md), or an inherited entry) is already covered by the namespace's blanket impl, so a second, more specific impl for that same key overlaps it. The overlap takes two shapes. - -The **context-level** shape is a context that joins a namespace and then directly wires a path the namespace registers: - -```rust -#[cgp_impl(new GreetHello)] -#[default_impl(@app.GreeterComponent in AppNamespace)] // AppNamespace binds this path -impl Greeter { /* … */ } - -delegate_components! { - App { - namespace AppNamespace; - - @app.GreeterComponent: GreetBye, // tries to override a path the namespace binds - } -} -``` - -The **namespace-level** shape is a child namespace that inherits a parent and redefines one of the parent's keys: - -```rust -cgp_namespace! { - new BaseNs { - GreeterComponent: GreetHello, - } -} - -cgp_namespace! { - new ChildNs: BaseNs { - GreeterComponent: GreetBye, // tries to override an inherited entry - } -} -``` - -Both reduce to "a specific entry for a key the namespace's blanket impl already covers." In the context case the blanket impl is the `namespace N;` forwarding `impl DelegateComponent for App where Key: N`; in the namespace case it is the inheritance forwarding `impl ChildNs
for Key where Key: BaseNs<…>`. Either way, CGP lowers both the blanket impl and the specific entry faithfully and cannot see from one macro invocation that they claim the same key, so it defers the overlap to the compiler. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **structural** class reported as **[`E0119`](../error_codes/e0119.md) conflicting implementations**, with the two-caret shape — "first implementation here" on the namespace's blanket source, and "conflicting implementation" on the specific entry — landing on the entries the user wrote. What the specific side of the overlap *names* is the signature that tells this class from the generic-versus-generic [overlapping namespace forwarding](namespace-forwarding-conflict.md): here one impl is keyed on a *concrete* key. The two shapes differ in the details, and recognizing each on sight tells a reader which override they attempted. - -The **context-level** shape produces a **pair** of `E0119`s, because a context is wired through both the `DelegateComponent` table and the `IsProviderFor` forwarding: one conflict on `DelegateComponent>` for `App` and one on `IsProviderFor, _, _>` for `App`, the conflicting key expanded into the long `PathCons>` path type (read the caret, not the type). This shape additionally carries a `note: downstream crates may implement trait IsProviderFor, _, _> for type GreetHello` (and for the overriding provider), which is `rustc` making its coherence reasoning explicit: whether the namespace's blanket forwarding and the direct path entry overlap hinges on whether the redirect's delegate provider implements `IsProviderFor` for that path, a bound a downstream crate could add, so the compiler cannot rule the overlap out and cites the hypothetical impl. That future-compatibility (negative-reasoning) rule is the same one [RFC 2451](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) formalizes and that the [conflicting wiring](conflicting-wiring.md) class explains in full. - -The **namespace-level** shape produces a **single** `E0119` — `conflicting implementations of trait ChildNs<_> for type GreeterComponent` — and **no** downstream note. There is only one conflict because a `cgp_namespace!` block emits only its own lookup-trait impls (`ChildNs
for Key`), never the context-side `DelegateComponent`/`IsProviderFor` pair, so nothing doubles it. There is no downstream note because the overlap is provable locally: the inheritance blanket impl covers `GreeterComponent` exactly when `GreeterComponent: BaseNs<…>`, an impl the same crate already emitted for the parent, so the compiler constructs the conflict without reasoning about any future impl. The self type of the conflict is the *component marker* (`GreeterComponent`), not the context, which is the tell that the collision is inside the namespace's own table rather than on a context. - -## Where the root cause is - -The root cause is **present and precise** in both shapes: the two carets name the namespace source and the overriding entry, and the error code names the collision. This is a structural class with no note chain to walk and nothing suppressed. The one reading skill it asks for is to look past the expanded `PathCons>` key type on the context-level shape and trust the caret, and to read the `downstream crates may implement …` note as coherence's *reason*, not a second, separate problem. What neither shape states is the CGP-specific remedy, since the message frames a namespace override decision as a bare coherence conflict. - -## How cargo-cgp presents it - -`cargo-cgp` rewrites the two shapes differently, because it recognizes only one of them today. The **context-level** shape is rewritten: keeping `E0119`, it collapses the `DelegateComponent` + `IsProviderFor` pair into one message, suppresses the `downstream crates may implement …` note, resugars the `PathCons>` key to its bare `@app.…` path, and stamps **`[CGP-E005]` overlapping wiring** — `` [CGP-E005] `App` cannot wire `@app.GreeterComponent.*` that is already set through `AppNamespace` ``. That headline names the overriding entry, the path it claims, and the namespace that already binds it, so the reader sees the override decision the raw coherence conflict only implied; the two `rustc` carets are kept, this being a structural class with no `root cause:` tree. - -The **namespace-level** shape is a **pass-through**: `cargo-cgp` does not yet rewrite it, so the raw `` conflicting implementations of trait `ChildNs<_>` for type `GreeterComponent` `` is what the reader sees, uncoded. The tell for the reader is the same as in the raw diagnostic — the self type is a *component marker* and the trait is the child namespace, marking the collision as inside the namespace's own table. The distinction that decides which the tool does is whether the collision is on a context (rewritten) or on a namespace trait (passed through); closing that gap would fold the namespace-level shape into `[CGP-E005]` as well. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -The fix depends on which override was attempted, and both follow one rule: **a namespace entry, once bound, cannot be overridden — only a path the namespace leaves unbound is overridable.** - -For the **context-level** shape, override by targeting a path the namespace *routes to* but does not itself *terminate*: register the component's [`#[prefix]`](../../reference/macros/cgp_namespace.md) redirect in a base namespace the context inherits, and leave the leaf path unclaimed so the context can supply it directly. If the namespace genuinely binds the path (a `:` body entry or a `#[default_impl]`), it is not overridable on the context — change it in the namespace instead, or move the binding out of the namespace so the leaf stays open. The [namespaces guide](../../guides/namespaces-and-prefixes.md) works this through: `MockApp` overrides `@app.finance.MoneyTransferrerComponent` precisely because `MockNamespace` deliberately does not register that path. - -For the **namespace-level** shape, do not bind the key in the base and redefine it in the child. To vary a key per configuration, leave it *unbound* in the shared base namespace and bind it in each inheriting namespace, so each child supplies the key without overriding an inherited one — the separation the guide recommends between a base namespace that describes an application's *structure* and inheriting namespaces that each describe one *configuration*. - -## Notes for tooling - -The **namespace-level** shape is the gap that remains: `cargo-cgp` passes it through uncoded (above), so a post-processor that recognizes the self type is a *component marker* and the trait is the child namespace could fold it into `[CGP-E005]` too and report "`ChildNs` cannot override `GreeterComponent`, which it inherits from `BaseNs`; leave it unbound in `BaseNs` to vary it per child." The context-level shape already gets the full `[CGP-E005]` rewrite, so nothing more is needed there. - -## Backing fixtures - -The `.rust.stderr` snapshot pins the raw `E0119` shape and the `.cgp.stderr` the reshaped (or passed-through) form. - -- [`wiring/namespace-paths/override_registered_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/override_registered_path.rs) — the context-level shape: a context joining `AppNamespace` overrides a path bound with `#[default_impl]`; the `.rust.stderr` pins the `E0119` pair on `DelegateComponent>` and `IsProviderFor, _, _>` for `App` with the expanded path type and the `downstream crates may implement` note, the `.cgp.stderr` the single `[CGP-E005]` headline with the resugared `@app.GreeterComponent.*` path. -- [`wiring/namespace-paths/inherited_override_conflict.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/inherited_override_conflict.rs) — the namespace-level shape: a child namespace redefining an inherited key; both snapshots agree — the single `E0119` on `ChildNs<_>` for `GreeterComponent`, with "first implementation here" on the inherited parent and no downstream note, passes through uncoded (the gap noted above). - -## Related - -- [Overlapping namespace forwarding](namespace-forwarding-conflict.md) — the sibling namespace `E0119`, a *blanket*-versus-blanket overlap over every key (fully-generic `DelegateComponent<_>`); contrast it by the concrete key one side names here. -- [Conflicting wiring](conflicting-wiring.md) — the general `E0119`/`E0428` class for a key or name declared twice, and the full account of the RFC 2451 coherence reasoning behind the `downstream crates may implement` note. -- [Orphan-rule violation](orphan-rule.md), [Wiring cycle](wiring-cycle.md), [Namespace inheritance cycle](namespace-inheritance-cycle.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. -- [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md) (and its Known issues), [`DefaultNamespace`](../../reference/traits/default_namespace.md), and the [namespaces guide](../../guides/namespaces-and-prefixes.md) — the inherit-and-override mechanics and the rule that a bound entry is not overridable. -- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0119` entry in the decoder. diff --git a/docs/errors/wiring/orphan-rule.md b/docs/errors/wiring/orphan-rule.md deleted file mode 100644 index c4764a87..00000000 --- a/docs/errors/wiring/orphan-rule.md +++ /dev/null @@ -1,68 +0,0 @@ -# Orphan-rule violation - -A generated impl registers a component into a *foreign* namespace with no local type anywhere in the impl, which Rust's orphan rule forbids, so the expansion fails with `E0210` (or `E0117`) — the failure a crate hits when it tries to add namespace wiring for a namespace, component, or path it does not own. - -## What triggers it - -The mistake is registering into a namespace whose *trait* the crate does not own, keyed on a type the crate also does not own — an impl of a foreign trait for a foreign type. A namespace registration lowers to `impl Namespace for Key`, and Rust accepts a foreign-trait impl only when a local type covers its parameters. When both the namespace and the key are foreign, nothing is local, so the orphan rule rejects it. Three constructs register into a namespace, and each hits the rule the same way when nothing local is in reach. - -The first is a **prefixed `#[default_impl]`**, where the component carries `#[prefix(@app in …)]` so its namespace key is a path built from the `cgp`-owned `PathCons`/`Symbol` spine and the upstream component marker — every element foreign to a downstream crate: - -```rust -// In a downstream crate, for an upstream prefixed component `Announcer`: -#[cgp_impl(new AnnounceQuietly)] -#[default_impl(@app.AnnouncerComponent in AppNamespace)] // AppNamespace + path all foreign -impl Announcer -where - Self: HasName, -{ - fn announce(&self) -> String { format!("(psst, {})", self.name()) } -} -``` - -The second is an **unprefixed `#[default_impl]` keyed on a foreign component marker**, which shows the restriction is not about prefixes: `#[default_impl(GreeterComponent in AppNamespace)]` for a foreign, unprefixed `GreeterComponent` expands to `impl AppNamespace for GreeterComponent` — a bare foreign marker as the key, still foreign trait for foreign type. - -The third is a **`cgp_namespace!` block without `new`** that re-opens a foreign namespace to add an entry. Omitting `new` tells the macro the namespace trait exists elsewhere and to emit only the entry impls, so `cgp_namespace! { AppNamespace { GreeterComponent => @foo } }` for a foreign `AppNamespace` emits `impl
AppNamespace
for GreeterComponent { type Delegate = … }` — again foreign trait for foreign key. - -Every form reduces to the same fact: a foreign namespace trait implemented for a foreign key, with no local type to satisfy the orphan rule. This is a whole-program coherence fact CGP cannot see from the macro invocation, so it lowers the impl faithfully and defers to the compiler. The orphan-*safe* counterpart is owning *either* end — a crate may register a *local* component's marker into a foreign namespace (the key is local), or add entries to a namespace whose trait it owns; only when both are foreign does the impl become an orphan. - -## The raw diagnostic - -This section describes what plain `cargo check` prints; `cargo-cgp` now reshapes this class into a CGP-framed message (see [How cargo-cgp presents it](#how-cargo-cgp-presents-it)), so what follows is the raw baseline the tool starts from. The compiler reports **`E0210`** — "type parameter `…` must be used as the type parameter for some local type" — naming the impl's uncovered table parameter, with two explanatory notes: "implementing a foreign trait is only possible if at least one of the types for which it is implemented is local," and "only traits defined in the current crate can be implemented for a type parameter." Which parameter is named, and where the caret lands, follows the construct that generated the impl: a `#[default_impl]` names **`__Components__`**, lands the caret on the `#[cgp_impl]` attribute, and attributes the error to the `cgp_impl` macro; a `cgp_namespace!` re-open names **`__Table__`** (the namespace table parameter), lands the caret on the whole `cgp_namespace!` block, and attributes the error to the `cgp_namespace` macro. The parameter differs only because the two constructs name their table parameter differently; the violation is identical. Depending on the exact shape of the generated impl, the sibling orphan error **`E0117`** ("only traits defined in the current crate can be implemented for arbitrary types") can appear instead; both are the orphan rule rejecting the same foreign-trait-for-foreign-type impl. - -The rule the compiler is enforcing here is coherence, and understanding it explains why the error frames the fix as a matter of *ownership*. Coherence requires that for any trait and type there is at most one impl, and the orphan rule preserves that across crates by forbidding an impl of a foreign trait unless a local type is *covered* by it — appears before any uncovered type parameter. Were an orphan impl allowed, two unrelated crates could each implement the foreign trait for the foreign type in incompatible ways, and adding a dependency could silently break a build; the orphan rule rejects the impl up front precisely so that can never happen. `E0210` is the specific form of this rule for an impl whose only "type" in the covering position is a bare type parameter (`__Components__`), which no local type covers. The current rule and its wording come from [RFC 2451 (re-rebalancing coherence)](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html); the [`E0210`](../error_codes/e0210.md) reference summarizes it, alongside its sibling [`E0117`](../error_codes/e0117.md). - -## Where the root cause is - -The mechanical cause is **present** — the error names the foreign trait and the offending type parameter — but the *actionable* cause is CGP-specific and the diagnostic does not state it. What the compiler cannot say is that the impl is foreign because a namespace registration landed on a namespace and key the crate does not own, nor that the remedy is a matter of crate ownership. So while this is not a hidden class, reading the raw `E0210` leaves a user who does not know CGP's namespace mechanics without the fix; the gap is knowledge, not information the compiler withheld. - -## How cargo-cgp presents it - -`cargo-cgp` reshapes this class into a `[CGP-E011]` message that names the foreign namespace and key rather than the machinery parameter. The header reads `` cannot register the foreign into the foreign namespace `` `` — where `` is `` component `GreeterComponent` `` for a bare marker or `` path `@app.AnnouncerComponent` `` for a prefix path (resugared from its `PathCons>` spine) — and the raw coherence notes are replaced by one `help` carrying the ownership-based fix from [Resolving it](#resolving-it) below. The caret still points at the offending `#[cgp_impl]` attribute or `cgp_namespace!` block, and the Rust code stays `E0210`/`E0117`, kept alongside the `[CGP-E011]` tag. - -The tool recovers what it names from the compiler, not the error text: it finds the offending impl at the caret, confirms it is a foreign namespace trait — recognized by the single-`Delegate` fingerprint every `cgp_namespace!` trait and the built-in `DefaultNamespace`/`DefaultImpls…` share — implemented for a foreign key, and reads the trigger off the impl's own parameter (`__Table__` for a `cgp_namespace!` re-open, `__Components__` for a `#[default_impl]`/`#[prefix]` registration) so the `help` names the fix that fits: keying the registration on a local component (or registering it from the namespace's crate) for a registration, and inheriting the namespace into a new local one for a re-open. The three triggers are pinned by the fixtures under [`acceptable/wiring/orphan/`](https://github.com/contextgeneric/cargo-cgp/tree/main/tests/ui/acceptable/wiring/orphan); `[CGP-E011]` is catalogued in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -Own one end of the impl. Register the default from the crate that owns the namespace, or key it on a **local** component whose marker the registering crate owns (`#[default_impl(LocalComponent in Namespace)]`), which satisfies the orphan rule because a local type is present. When the wiring genuinely must live downstream of the namespace, use a namespace *body* entry rather than a per-component `#[default_impl]`, per the [namespaces guide](../../guides/namespaces-and-prefixes.md). To *extend* a foreign namespace, do not re-open it with a bare `cgp_namespace!` block — define a **new local namespace that inherits it** (`cgp_namespace! { new Local: ForeignNamespace { … } }`), which is orphan-safe because every emitted impl is for the local namespace trait. See [`DefaultNamespace`](../../reference/traits/default_namespace.md) for the orphan-safe registration patterns. - -## Notes for tooling - -`cargo-cgp` reshapes this class, as [How cargo-cgp presents it](#how-cargo-cgp-presents-it) describes: it recognizes the shape — a namespace trait (a `#[cgp_namespace]` trait, `DefaultNamespace`, or a `DefaultImpls…`, told apart by their single-`Delegate` fingerprint) implemented for a foreign component marker or a `PathCons>` spine — and translates the generic orphan message into the CGP remedy: own one end, by keying the registration on a local component, registering it from the namespace's own crate, or (for a `cgp_namespace!` re-open) inheriting the namespace into a new local one. The raw diagnostic was accurate but framed a CGP wiring decision as a bare coherence rule, so the value the rewrite adds is the translation, not the extraction. - -## Backing fixtures - -Each fixture sits under `acceptable/wiring/orphan/`, where its `.cgp.stderr` pins the reshaped `[CGP-E011]` output and its `.rust.stderr` the raw `E0210` baseline. - -- [`acceptable/wiring/orphan/default_impl_foreign_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/orphan/default_impl_foreign_prefix_path.rs) — a downstream crate registering a `#[default_impl]` for an upstream *prefixed* component into the upstream namespace, so the key is a foreign `PathCons>` path; the reshaped snapshot names the `` path `@app.AnnouncerComponent` `` and the foreign namespace. -- [`acceptable/wiring/orphan/default_impl_foreign_component.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/orphan/default_impl_foreign_component.rs) — the same `#[default_impl]` orphan keyed on a foreign *unprefixed* component marker rather than a path, showing the restriction is not specific to prefixes; the snapshot names the `` component `GreeterComponent` `` instead. -- [`acceptable/wiring/orphan/reopen_foreign_namespace.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/orphan/reopen_foreign_namespace.rs) — a `cgp_namespace!` block without `new` re-opening a foreign namespace; its `__Table__` trigger selects the inherit-a-new-namespace `help`, with the caret on the whole `cgp_namespace!` block. - -The orphan-*safe* counterpart is the positive fixture [`ok/cross_crate_wiring.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/ok/cross_crate_wiring.rs), which compiles clean: its auxiliary crates (`cgp-test-crate-a` upstream, `cgp-test-crate-b` downstream) wire a foreign component to a foreign provider, join an upstream namespace, and register a *local* component into the upstream namespace with `#[default_impl]` — every cross-crate impl coherent because one end is always local. - -## Related - -- [Conflicting wiring](conflicting-wiring.md), [Wiring cycle](wiring-cycle.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. -- [Overlapping namespace forwarding](namespace-forwarding-conflict.md), [Namespace override conflict](namespace-override-conflict.md), [Namespace inheritance cycle](namespace-inheritance-cycle.md), [Unregistered namespace path](../checks/unregistered-namespace-path.md) — the other namespace-specific failure classes. -- [`DefaultNamespace`](../../reference/traits/default_namespace.md) and [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md) — the namespace mechanics behind the restriction. -- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0210`/`E0117` entry in the decoder. diff --git a/docs/errors/wiring/unconstrained-generic.md b/docs/errors/wiring/unconstrained-generic.md deleted file mode 100644 index fe859454..00000000 --- a/docs/errors/wiring/unconstrained-generic.md +++ /dev/null @@ -1,49 +0,0 @@ -# Unconstrained generic - -A per-entry generic parameter appears only in the provider value and never reaches the key, so the generated impl leaves the parameter unconstrained and the compiler rejects it with `E0207`. - -## What triggers it - -A `delegate_components!` entry may introduce its own generic parameters, but a per-entry generic is well-formed only when it reaches the *key* — where `DelegateComponent>` binds it. Writing one that appears only in the provider value never binds it, and CGP lowers the entry faithfully rather than second-guessing it, so the compiler rejects the free parameter exactly as it would a hand-written impl with an unused parameter. - -```rust -delegate_components! { - Person { - GreeterComponent: GreetWith, // T is in the value, never the key - } -} -// lowers to an impl with an unconstrained parameter: -impl DelegateComponent for Person { - type Delegate = GreetWith; // T constrains nothing -} -``` - -The same shape arises when a *generic* provider is registered as a per-type default, since the provider's parameter lands only in the `Delegate` associated-type position. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. The compiler reports **`E0207`** — "the type parameter `T` is not constrained by the impl trait, self type, or predicates" — with the caret on the `` the user wrote in the entry. The class is well-localized rather than cascading, but it is not quite a single line: the entry lowers into more than one generated position carrying `T`, so the same `E0207` prints twice, each with a different `help:` — one offering to move `T` onto the `Person` type, one to remove the unused parameter. Both carets land on the same `` and name the same cause, so there is no note chain and nothing to trace. - -The rule behind it is that an impl parameter must be *determined* by the impl, and knowing why makes the fix obvious. Rust requires every generic parameter on an impl to appear in the implemented trait, in the self type, or in a `where`-clause predicate that pins it as an associated type — one of the three "constrained" positions the message names — because otherwise, given a trait reference, the compiler could not decide which `T` the impl is for. Here `T` reaches only the `Delegate` *value* (`GreetWith`), an associated-type position on the right of the `=`, which does not constrain the impl: `DelegateComponent` names no `T`, so any `T` would satisfy the header equally. Permitting that would also break coherence, since a downstream crate adding another `GreetWith` would make the choice ambiguous. This is the rule introduced by [RFC 447](https://github.com/rust-lang/rfcs/pull/447) ("prohibit unused type parameters in impls"); the [`E0207`](../error_codes/e0207.md) reference summarizes it. It is the same negative-reasoning concern that underlies the [orphan rule](orphan-rule.md) and coherence conflicts — an impl must be resolvable no matter what impls other crates add later. - -## Where the root cause is - -The root cause is **present and precise**: the caret sits on the offending generic parameter, and the message states exactly why it is rejected. This is the most localized class in the catalog — the diagnostic needs no tracing and hides nothing. The only thing the raw message lacks is the CGP-specific remedy, since it describes the constraint in impl terms rather than in terms of the wiring entry. - -## How cargo-cgp presents it - -`cargo-cgp` does not yet rewrite this class — it passes rustc's diagnostic through unchanged. For the fixture, the tool's `.cgp.stderr` is byte-for-byte its raw `.rust.stderr`: both `E0207` errors, both `help:` notes, no `[CGP-Exxx]` code stamped. That pass-through is why the fixture sits in cargo-cgp's `usability/` tier rather than `acceptable/`. The cause is already pinpoint-accurate at the caret, so nothing is buried — but the message still speaks in impl terms ("the type parameter `T` is not constrained"), where the actionable reading is a wiring one: the generic must reach the component *key* (` SomeKey: …`), not only the provider *value*. Restating the fix that way is the one improvement left for the tool; there is nothing to recover, only to translate. The codes it stamps on the classes it does rewrite are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -Make the generic reach the key so it is bound — introduce it on a key that carries it (` SomeKey: …`) rather than only on the value — or, when the intent was a single concrete wiring, register a concrete provider with no per-entry generic at all. For a generic provider registered as a per-type default, register a concrete provider instead, since the default position cannot bind the provider's parameter. - -## Backing fixtures - -- [`usability/wiring/constraints/unconstrained_generic.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/constraints/unconstrained_generic.rs) — a per-entry generic (` GreeterComponent: GreetWith`) that appears only in the value, lowering to an impl with an unconstrained `T`; its `.rust.stderr` pins the `E0207` carets on the `` (the error printed twice), and its `.cgp.stderr` is identical — the pass-through that places the fixture in the `usability/` tier, since cargo-cgp does not yet restate this class in wiring terms. - -## Related - -- [Conflicting wiring](conflicting-wiring.md), [Orphan-rule violation](orphan-rule.md), [Wiring cycle](wiring-cycle.md) — the sibling structural classes. -- [`delegate_components!`](../../reference/macros/delegate_components.md) and [`DelegateComponent`](../../reference/traits/delegate_component.md). -- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0207` entry in the decoder. diff --git a/docs/errors/wiring/wiring-cycle.md b/docs/errors/wiring/wiring-cycle.md deleted file mode 100644 index 744af30b..00000000 --- a/docs/errors/wiring/wiring-cycle.md +++ /dev/null @@ -1,62 +0,0 @@ -# Wiring cycle - -A delegation chases its own tail — a component wired to a provider whose resolution routes back to that same component — so when the wiring is forced through a check the trait solver overflows with `E0275`. - -## What triggers it - -The classic cycle is delegating a component to [`UseContext`](../../reference/providers/use_context.md) when the context's only implementation of that component *is* that delegation. `UseContext` implements the provider trait by routing back through the context's own consumer-trait impl, but that consumer impl exists only via this delegation to `UseContext`, so resolving the provider trait requires resolving the consumer trait, which requires the provider trait again. - -```rust -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[derive(HasField)] -pub struct Person { pub name: String } - -// Person's only source of CanGreet is this delegation to UseContext, which -// resolves back to CanGreet — a cycle with no terminating provider. -delegate_components! { - Person { - GreeterComponent: UseContext, - } -} -``` - -CGP lowers the wiring faithfully and cannot see that the delegation is self-referential without a whole-program view, so it accepts the entry and defers the failure to the compiler. A mutual cycle between two components that each delegate through the other fails the same way. - -## The raw diagnostic - -This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. How the cycle surfaces depends on how it is exercised, and the two shapes are very different. Forcing the wiring through a [`check_components!`](../../reference/macros/check_components.md) drives the solver directly into the loop, and it overflows with **`E0275`** — "overflow evaluating the requirement `Person: IsProviderFor`" — followed by a note chain that *names the cycle*: `Person` to implement `Greeter`, then `CanGreet`, then `UseContext` to implement `IsProviderFor`, then back into `CanUseComponent`. The loop is visible in the chain. - -Exercised instead by a plain method call on the context, the same cycle does *not* overflow: the method probe treats the unresolvable requirement as simply unsatisfied and reports the [hidden `E0599`](../hidden/unsatisfied-dependency.md) — "method exists but its trait bounds were not satisfied" — with no hint that a cycle is the reason. So a wiring cycle is `E0275` when checked and a hidden `E0599` when called, and only the checked form actually reveals the cause. - -The [`E0275`](../error_codes/e0275.md) itself is ordinary `rustc` behavior. The trait solver follows a requirement's supporting bounds only to a bounded depth — the [default `recursion_limit` is 128](https://doc.rust-lang.org/reference/attributes/limits.html) — and raises `E0275` when it exceeds that depth without resolving, appending its standard `help: consider increasing the recursion limit`. That advice suits a merely *deep* obligation, but a true cycle never terminates at any depth, so the limit here is only ever hit, never cleared; the suggestion does not apply and should be read past. This is why the cycle must be exercised through a check to surface at all: the overflow is computed only when the solver is driven into the loop, and `#[cgp_component]`'s blanket impl otherwise lets the method probe abandon the requirement as unsatisfied without ever entering it. - -## Where the root cause is - -The cause — the cycle — is **present in the checked (`E0275`) form** and readable from the note chain: the requirement that overflows and the intervening `Greeter` / `CanGreet` / `UseContext` notes together trace the loop, so the participants (the component, the consumer trait, and `UseContext`) are all named. What the diagnostic does *not* state is the remedy. In the hidden (`E0599`) form the cause is absent, exactly as for any [hidden unsatisfied dependency](../hidden/unsatisfied-dependency.md) — which is itself a reason to reach for a check when a `UseContext` wiring is suspect. - -## How cargo-cgp presents it - -`cargo-cgp` recognizes the checked overflow and rewrites it into a statement of what actually went wrong. It keeps the `E0275` code but replaces the headline with `[CGP-E010] the wiring for the consumer trait \`CanGreet\` on context \`Person\` never resolves — the lookup recurses without terminating`, drops the `Greeter` / `CanGreet` / `UseContext` / `__CheckPerson` note chain entirely, and leaves the caret on the `GreeterComponent` entry inside the check block. A `help:` note names the usual cause — a component delegated to `UseContext` with no direct consumer-trait impl — and the two fixes: wire a real provider, or implement the consumer trait on the context. Where raw `rustc` hands you an overflow depth and a chain to decode, the tool states that the wiring recurses and why. - -The tool does more than restate the overflow when a cycle is tangled with a recoverable cause, and this is where its cycle guard earns its keep. In `mutual_cycle_with_cause` — `ProviderA` depends on `CanB`, `ProviderB` depends back on `CanA`, walked alongside a genuinely missing `width` field — the resolver cuts the `CanA → CanB → CanA` loop and follows the other branch to the leaf, leading with `[CGP-E002] the provider trait \`ProviderA\` with context \`App\` is not implemented for provider \`DoA\`` over a `root cause: [CGP-E106] missing field \`width\` on \`App\`` dependency tree (`[CGP-E101]` consumer-impl hop → `[CGP-E102]` provider-impl hop → `[CGP-E105]` `HasWidth` hop → the leaf). The cycle is not the headline; the concrete cause reachable down the non-cyclic branch is. Because the same mistake hides as `E0599` when reached by a method call, the tool promotes it the way it promotes any [hidden dependency](../hidden/unsatisfied-dependency.md) — synthesizing a check — which turns the hidden form into the `E0275` it then rewrites to `[CGP-E010]`. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). - -## Resolving it - -Break the cycle by wiring the component to a concrete provider that terminates the lookup rather than routing back to the context. `UseContext` is only safe as the *inner* provider of a higher-order provider or where the context supplies the component through some other impl; delegating a component's sole implementation to `UseContext` is always a cycle. When the overflow is genuinely a depth problem rather than a true cycle, raising `#![recursion_limit = "…"]` is the wrong fix here — the requirement never terminates, so no limit is high enough. - -## Backing fixtures - -- [`acceptable/wiring/constraints/use_context_cycle.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/constraints/use_context_cycle.rs) — a component wired to `UseContext` with no terminating provider, forced through a `check_components!`; its `.rust.stderr` pins the raw `E0275` overflow and the `Greeter` → `CanGreet` → `UseContext` note chain, and its `.cgp.stderr` the `[CGP-E010]` "never resolves" rewrite with the chain dropped and the caret held on the `GreeterComponent` entry. -- [`acceptable/wiring/constraints/mutual_cycle_with_cause.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/constraints/mutual_cycle_with_cause.rs) — a two-component cycle (`ProviderA` ↔ `ProviderB`) walked alongside a genuinely missing `width` field; its `.rust.stderr` is the raw overflow, and its `.cgp.stderr` shows the resolver's cycle guard cutting the loop and leading with `[CGP-E002]` over a `root cause: [CGP-E106] missing field \`width\`` tree — the cause down the non-cyclic branch rather than the cycle. - -## Related - -- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the `E0599` shape the same cycle takes when exercised by a method call instead of a check. -- [Namespace inheritance cycle](namespace-inheritance-cycle.md) — the sibling `E0275` cycle through circular namespace inheritance rather than `UseContext` delegation; that one is caught *eagerly* at the namespace definitions, where this one is lazy. -- [Conflicting wiring](conflicting-wiring.md), [Orphan-rule violation](orphan-rule.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. -- [`UseContext`](../../reference/providers/use_context.md) — the provider whose misuse most often causes the cycle. -- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0275` entry in the decoder. diff --git a/docs/examples/README.md b/docs/examples/README.md deleted file mode 100644 index 95ef84bf..00000000 --- a/docs/examples/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# CGP Examples - -This directory holds self-contained worked examples of CGP in use. Each document develops one realistic use case end to end — the contexts, the components and providers, and the wiring that connects them — so that the example reads as a coherent program rather than a list of isolated snippets. The examples are the preferred source of code snippets for the rest of the knowledge base: when a [reference document](../reference/README.md) needs a worked illustration, it should draw on the patterns shown here so that the same vocabulary and the same running example appear everywhere a reader looks. - -## How examples differ from reference documents - -An example demonstrates a *use case and the design patterns that solve it*, whereas a reference document explains a *single construct completely*. The two are complementary. A reference document for `#[cgp_fn]` states its syntax, its exact expansion, and its corner cases; an example shows `#[cgp_fn]` working alongside `delegate_components!` and higher-order providers to actually compute something. Because the constructs are documented in full in the reference, an example does not re-explain them. It carries only enough prose to make the code legible, leaves a short note on which CGP concept each step demonstrates, and links to the reference document that owns that concept. A reader who wants the mechanics follows the link; a reader who wants the shape of a solution stays in the example. - -The audience for these examples is an agent writing expanded documentation — a tutorial, a guide, a how-to. An example is the raw material such an agent draws on: a verified, idiomatic progression it can quote, adapt, and elaborate, confident that the code reflects current CGP. The examples therefore optimize for being *quotable and correct* rather than exhaustive. - -## The catalog - -The authoring rules for examples, including how to add a new one, live in [../AGENTS.md](../AGENTS.md). - -- [Area calculation](area-calculation.md) — computing the area of several shapes, progressing from field-driven functions to a wireable area component with composable higher-order providers. -- [Shell-scripting DSL](shell-scripting-dsl.md) — a type-level DSL whose programs are types interpreted at compile time, progressing from a fixed CLI program through the handler component and its namespace wiring to a custom context and a language extension. -- [Profile picture lookup](profile-picture.md) — fetching a user's profile picture across a database query and an object-storage download, progressing from field-driven async functions through impl-only generics that vary the database engine to a storage component wired per context. -- [Money-transfer API](money-transfer-api.md) — the backend for a balance-query and funds-transfer web service, progressing from abstract domain types and a status-coded error component through a per-endpoint-dispatched async handler and reusable wrappers to a namespace-organized context wiring served over HTTP with a recovered `Send` bound. -- [Application builder](application-builder.md) — assembling an application context from independent per-subsystem builder providers, progressing from a hand-written constructor through merged builder outputs to swapping subsystems and producing several application variants from one builder via the [extensible builder pattern](../concepts/extensible-records.md). -- [Expression interpreter](expression-interpreter.md) — a modular interpreter for an arithmetic language that solves the expression problem, progressing from a closed enum through per-variant evaluation providers and a second to-Lisp operation to code-based dispatch and an extended language, using the [extensible visitor pattern](../concepts/extensible-variants.md). -- [Extensible shapes](extensible-shapes.md) — operations over geometric shapes modeled as enum variants, progressing from an auto-dispatched per-variant operation through mutating and argument-taking methods and enum upcasting/downcasting to context-wired dispatch with the [visitor combinators](../reference/providers/dispatch_combinators.md); the non-recursive companion to the expression interpreter. -- [Social media app](social-media-app.md) — the CRUD backend for a users-and-posts service, progressing from coarse per-domain manager traits through fine-grained per-operation traits and a higher-order input filter to provider bundles and namespace-grouped wiring that keeps the top-level configuration short as the component count grows. -- [Modular serialization](modular-serialization.md) — Serde's `Serialize` and `Deserialize` rebuilt as CGP components, progressing from the two serialization components through a family of overlapping per-type providers to two contexts that encode the same data into different JSON formats by a few wiring lines, and a context-supplied arena capability for deserialization, demonstrating the [coherence](../concepts/coherence.md) bypass on a real-world trait. diff --git a/docs/examples/application-builder.md b/docs/examples/application-builder.md deleted file mode 100644 index e1ef07f9..00000000 --- a/docs/examples/application-builder.md +++ /dev/null @@ -1,336 +0,0 @@ -# Application builder - -This example assembles an application context — a struct holding a database pool, an HTTP client, and an AI agent — from independent builder providers that each construct one subsystem and know nothing of the final struct or of each other. It progresses from a hand-written constructor that grows unmanageably, through a builder provider per subsystem, to a builder context that merges them all, and finally to swapping subsystems and producing several application variants from one builder. It is a template for any use case where a context is configured from independently-evolving parts that should compose without a central constructor. - -The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: - -- assembling a struct from independent contributions — [extensible records](../concepts/extensible-records.md) and the [extensible builder pattern](../concepts/dispatching.md) -- a struct that can be built field by field and merged — [`#[derive(BuildField)]`](../reference/derives/derive_build_field.md) with [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md), and `build_from` from [casting](../reference/traits/cast.md) -- each subsystem builder is a handler — [`Handler` / `CanHandle`](../reference/components/handler.md) in the [handler family](../concepts/handlers.md) -- writing a builder provider — [`#[cgp_impl]`](../reference/macros/cgp_impl.md) reading config through [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) -- the abstract error and raising into it — [`HasErrorType`](../reference/components/has_error_type.md) and [`CanRaiseError`](../reference/components/can_raise_error.md) -- merging builder outputs into the target — [`BuildAndMergeOutputs`](../reference/providers/dispatch_combinators.md) -- wiring and checking a context — [`delegate_components!`](../reference/macros/delegate_components.md) and [`check_components!`](../reference/macros/check_components.md) -- choosing among build targets at the type level — [`UseDelegate`](../reference/providers/use_delegate.md) - -All snippets assume `use cgp::prelude::*;`, the handler items come from `cgp::extra::handler`, and the builder dispatcher from `cgp::extra::dispatch`. - -## The constructor that grows - -The application context is an ordinary struct, and the direct way to build it is a constructor that initializes every field at once: - -```rust -#[derive(HasField, HasFields, BuildField)] -pub struct App { - pub sqlite_pool: SqlitePool, - pub http_client: Client, - pub open_ai_client: openai::Client, - pub open_ai_agent: Agent, -} - -impl App { - pub async fn new( - db_options: &str, - db_journal_mode: &str, - http_user_agent: &str, - open_ai_key: &str, - open_ai_model: &str, - llm_preamble: &str, - ) -> Result { - let journal_mode = SqliteJournalMode::from_str(db_journal_mode)?; - let db_options = SqliteConnectOptions::from_str(db_options)?.journal_mode(journal_mode); - let sqlite_pool = SqlitePool::connect_with(db_options).await?; - - let http_client = Client::builder() - .user_agent(http_user_agent) - .connect_timeout(Duration::from_secs(5)) - .build()?; - - let open_ai_client = openai::Client::new(open_ai_key); - let open_ai_agent = open_ai_client.agent(open_ai_model).preamble(llm_preamble).build(); - - Ok(Self { sqlite_pool, http_client, open_ai_client, open_ai_agent }) - } -} -``` - -Every subsystem's setup lives in one function, so each new field widens the parameter list and every team touches the same constructor. The deriving line is the first move away from that: `App` derives [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md) and [`#[derive(BuildField)]`](../reference/derives/derive_build_field.md), which expose it as a [product of named fields](../concepts/extensible-records.md) and generate a partial-record builder, so the struct can be filled in field by field instead of all at once. - -## A builder provider for one subsystem - -Each subsystem becomes its own provider that constructs a small output struct. The SQLite builder reads its configuration from the context and produces a `SqliteClient`: - -```rust -#[cgp_auto_getter] -pub trait HasSqliteOptions { - fn db_options(&self) -> &str; - fn db_journal_mode(&self) -> &str; -} - -#[derive(HasField, HasFields, BuildField)] -pub struct SqliteClient { - pub sqlite_pool: SqlitePool, -} - -#[cgp_impl(new BuildSqliteClient)] -#[uses(HasSqliteOptions, CanRaiseError)] -#[use_type(HasErrorType.Error)] -impl Handler { - type Output = SqliteClient; - - async fn handle( - &self, - _code: PhantomData, - _input: Input, - ) -> Result { - let journal_mode = - SqliteJournalMode::from_str(self.db_journal_mode()).map_err(Self::raise_error)?; - let db_options = SqliteConnectOptions::from_str(self.db_options()) - .map_err(Self::raise_error)? - .journal_mode(journal_mode); - let sqlite_pool = SqlitePool::connect_with(db_options) - .await - .map_err(Self::raise_error)?; - - Ok(SqliteClient { sqlite_pool }) - } -} -``` - -`BuildSqliteClient` is a [`Handler`](../reference/components/handler.md) provider written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), so it reads like a method on the builder context while staying generic over it. It does not know the final `App` type — only that its context can supply SQLite options (through the [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) getter `HasSqliteOptions`, satisfied by any context with the matching fields) and can raise an `sqlx::Error` into its own abstract error via [`CanRaiseError`](../reference/components/can_raise_error.md). Its output `SqliteClient` derives the same record traits as `App`, which is what lets its single field be merged into the larger struct later. - -The other subsystems follow the same shape. The HTTP and OpenAI builders each read their own config and produce their own output struct: - -```rust -#[cgp_auto_getter] -pub trait HasHttpClientConfig { - fn http_user_agent(&self) -> &str; -} - -#[derive(HasField, HasFields, BuildField)] -pub struct HttpClient { - pub http_client: Client, -} - -#[cgp_impl(new BuildHttpClient)] -#[uses(HasHttpClientConfig, CanRaiseError)] -#[use_type(HasErrorType.Error)] -impl Handler { - type Output = HttpClient; - - async fn handle(&self, _code: PhantomData, _input: Input) -> Result { - let http_client = Client::builder() - .user_agent(self.http_user_agent()) - .connect_timeout(Duration::from_secs(5)) - .build() - .map_err(Self::raise_error)?; - - Ok(HttpClient { http_client }) - } -} -``` - -```rust -#[cgp_auto_getter] -pub trait HasOpenAiConfig { - fn open_ai_key(&self) -> &str; - fn open_ai_model(&self) -> &str; - fn llm_preamble(&self) -> &str; -} - -#[derive(HasField, HasFields, BuildField)] -pub struct OpenAiClient { - pub open_ai_client: openai::Client, - pub open_ai_agent: Agent, -} - -#[cgp_impl(new BuildOpenAiClient)] -#[uses(HasOpenAiConfig)] -#[use_type(HasErrorType.Error)] -impl Handler { - type Output = OpenAiClient; - - async fn handle(&self, _code: PhantomData, _input: Input) -> Result { - let open_ai_client = openai::Client::new(self.open_ai_key()); - let open_ai_agent = open_ai_client - .agent(self.open_ai_model()) - .preamble(self.llm_preamble()) - .build(); - - Ok(OpenAiClient { open_ai_client, open_ai_agent }) - } -} -``` - -Each provider states exactly what it needs from the context through [`#[uses(...)]`](../reference/attributes/uses.md) as an [impl-side dependency](../concepts/impl-side-dependencies.md), and nothing more. A builder whose construction cannot fail — like `BuildOpenAiClient` — only imports the abstract error type with [`#[use_type(HasErrorType.Error)]`](../reference/attributes/use_type.md) so its output type aligns with the others, while a fallible one also lists the `CanRaiseError` it needs. - -## Merging the outputs into the application - -The builder context names the configuration fields and wires the providers together. It is a plain struct that derives [`HasField`](../reference/derives/derive_has_field.md) — which makes the getters above resolve against its fields — and wires the `HandlerComponent` to [`BuildAndMergeOutputs`](../reference/providers/dispatch_combinators.md): - -```rust -#[derive(HasField, Deserialize)] -pub struct FullAppBuilder { - pub db_options: String, - pub db_journal_mode: String, - pub http_user_agent: String, - pub open_ai_key: String, - pub open_ai_model: String, - pub llm_preamble: String, -} - -delegate_components! { - FullAppBuilder { - ErrorTypeProviderComponent: - UseAnyhowError, - ErrorRaiserComponent: - RaiseAnyhowError, - HandlerComponent: - BuildAndMergeOutputs< - App, - Product![ - BuildSqliteClient, - BuildHttpClient, - BuildOpenAiClient, - ]>, - } -} - -check_components! { - FullAppBuilder { - HandlerComponent: ((), ()), - } -} -``` - -`BuildAndMergeOutputs` is the heart of the [extensible builder pattern](../concepts/extensible-records.md): it starts an empty `App` builder, runs each provider in the list, merges each output struct into the builder with `build_from` from [casting](../reference/traits/cast.md), and finalizes the complete `App`. The merge is name-driven, so `SqliteClient`'s `sqlite_pool` field lands in `App`'s `sqlite_pool` field with no conversion written. The error wiring picks `anyhow::Error` as the abstract error through `UseAnyhowError` and lets the source errors raise into it through `RaiseAnyhowError`, satisfying the `CanRaiseError` bounds the providers declared. The [`check_components!`](../reference/macros/check_components.md) block asserts at compile time that the handler is wired for the unit `Code` and `Input` the build is invoked with — if any provider's required field were missing from `FullAppBuilder`, this would fail to compile rather than at runtime. - -Building the `App` is then one call. The builder is constructed from its config fields — or deserialized from a file, since it derives `Deserialize` — and `handle` runs the whole pipeline: - -```rust -pub async fn main() -> Result<(), Error> { - let builder = FullAppBuilder { - db_options: "file:./db.sqlite".to_owned(), - db_journal_mode: "WAL".to_owned(), - http_user_agent: "SUPER_AI_AGENT".to_owned(), - open_ai_key: "1234567890".to_owned(), - open_ai_model: "gpt-4o".to_owned(), - llm_preamble: "You are a helpful assistant".to_owned(), - }; - - let _app = builder.handle(PhantomData::<()>, ()).await?; - - Ok(()) -} -``` - -The `PhantomData::<()>` is the `Code` and the `()` the `Input`; neither is constrained here, so the unit type stands in for both. - -## Swapping a subsystem - -Because each builder is decoupled from the target struct, replacing one subsystem reuses everything else. An enterprise variant that uses Postgres instead of SQLite needs a new output struct and builder, plus a target struct that holds a `PgPool`: - -```rust -#[cgp_auto_getter] -pub trait HasPostgresUrl { - fn postgres_url(&self) -> &str; -} - -#[derive(HasField, HasFields, BuildField)] -pub struct PostgresClient { - pub postgres_pool: PgPool, -} - -#[cgp_impl(new BuildPostgresClient)] -#[uses(HasPostgresUrl, CanRaiseError)] -#[use_type(HasErrorType.Error)] -impl Handler { - type Output = PostgresClient; - - async fn handle(&self, _code: PhantomData, _input: Input) -> Result { - let postgres_pool = PgPool::connect(self.postgres_url()).await.map_err(Self::raise_error)?; - Ok(PostgresClient { postgres_pool }) - } -} -``` - -The new builder context swaps `BuildPostgresClient` in for `BuildSqliteClient` and reuses the HTTP and OpenAI builders unchanged: - -```rust -#[derive(HasField, HasFields, BuildField)] -pub struct App { - pub postgres_pool: PgPool, - pub http_client: Client, - pub open_ai_client: openai::Client, - pub open_ai_agent: Agent, -} - -#[derive(HasField, Deserialize)] -pub struct AppBuilder { - pub postgres_url: String, - pub http_user_agent: String, - pub open_ai_key: String, - pub open_ai_model: String, - pub llm_preamble: String, -} - -delegate_components! { - AppBuilder { - ErrorTypeProviderComponent: UseAnyhowError, - ErrorRaiserComponent: RaiseAnyhowError, - HandlerComponent: - BuildAndMergeOutputs< - App, - Product![ - BuildPostgresClient, - BuildHttpClient, - BuildOpenAiClient, - ]>, - } -} -``` - -Unlike feature flags, which force an either/or split at compile time, the SQLite and Postgres variants can coexist in the same codebase and even compile together, which keeps both paths tested. - -## Many applications from one builder - -A single builder can produce several application variants by dispatching on its `Code` parameter. Given a builder whose fields cover the needs of every variant, marker types name the build targets and [`UseDelegate`](../reference/providers/use_delegate.md) routes each one to its own provider list: - -```rust -pub struct BuildChatGptApp; -pub struct BuildAnthropicApp; -pub struct BuildAnthropicAndChatGptApp; - -delegate_components! { - AnthropicAndChatGptAppBuilder { - ErrorTypeProviderComponent: UseAnyhowError, - ErrorRaiserComponent: RaiseAnyhowError, - HandlerComponent: - UseDelegate, - BuildAnthropicApp: - BuildAndMergeOutputs, - BuildAnthropicAndChatGptApp: - BuildAndMergeOutputs, - }>, - } -} -``` - -Each marker selects a different target struct and provider list, and the `llm_preamble` field is shared by both the Anthropic and OpenAI builders with no coordination — a value-level dependency injected once and read by every provider that needs it. Choosing a variant is then a matter of which `Code` is passed to `handle`: - -```rust -let chat_gpt_app: App = builder.handle(PhantomData::, ()).await?; -let anthropic_app: AnthropicApp = builder.handle(PhantomData::, ()).await?; -let combined_app: AnthropicAndChatGptApp = - builder.handle(PhantomData::, ()).await?; -``` - -The same builder, the same config, three different application contexts — selected by type, dispatched at compile time, with the builder pipeline for each one assembled from the same decoupled providers. diff --git a/docs/examples/area-calculation.md b/docs/examples/area-calculation.md deleted file mode 100644 index fc3ecd39..00000000 --- a/docs/examples/area-calculation.md +++ /dev/null @@ -1,176 +0,0 @@ -# Area calculation - -This example computes the area of several shapes, progressing from a single field-driven function to a unified, wireable area-calculation component whose implementations compose through higher-order providers. It is a template for any use case where one operation has several interchangeable implementations chosen per context — the shapes here stand in for whatever set of variants an application needs to treat uniformly. - -The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: - -- context-generic functions — [`#[cgp_fn]`](../reference/macros/cgp_fn.md) with [implicit arguments](../concepts/implicit-arguments.md) -- importing capabilities — [`#[uses]`](../reference/attributes/uses.md) -- field access on contexts — [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) -- components and named providers — [`#[cgp_component]`](../reference/macros/cgp_component.md), [`#[cgp_impl]`](../reference/macros/cgp_impl.md), and the [consumer/provider trait duality](../concepts/consumer-and-provider-traits.md) -- wiring a context to providers — [`delegate_components!`](../reference/macros/delegate_components.md) -- composing providers — [higher-order providers](../concepts/higher-order-providers.md) with [`#[use_provider]`](../reference/attributes/use_provider.md) - -All snippets assume `use cgp::prelude::*;`. - -## A field-driven function - -The smallest unit of reuse is a function that reads its inputs from the context's fields instead of from explicit arguments. Marking `rectangle_area` with [`#[cgp_fn]`](../reference/macros/cgp_fn.md) and tagging its parameters [`#[implicit]`](../reference/attributes/implicit.md) turns it into a method available on any context that carries a `width` and a `height` field: - -```rust -#[cgp_fn] -pub fn rectangle_area( - &self, - #[implicit] width: f64, - #[implicit] height: f64, -) -> f64 { - width * height -} -``` - -A context becomes eligible by deriving [`HasField`](../reference/derives/derive_has_field.md), which generates the field accessors the implicit arguments resolve through. No per-context implementation is needed: - -```rust -#[derive(HasField)] -pub struct PlainRectangle { - pub width: f64, - pub height: f64, -} - -let area = PlainRectangle { width: 2.0, height: 3.0 }.rectangle_area(); -assert_eq!(area, 6.0); -``` - -## Building on another function - -A context-generic function can call another by importing it with [`#[uses]`](../reference/attributes/uses.md), which adds the imported capability as a hidden bound on the context rather than a visible parameter. Here `scaled_rectangle_area` reuses `rectangle_area` while contributing only its own `scale_factor` field: - -```rust -#[cgp_fn] -#[uses(RectangleArea)] -pub fn scaled_rectangle_area( - &self, - #[implicit] scale_factor: f64, -) -> f64 { - self.rectangle_area() * scale_factor * scale_factor -} -``` - -The imported name `RectangleArea` is the trait `#[cgp_fn]` derives from the `rectangle_area` function — a function `foo` generates a trait `Foo`. A context that adds the extra field can use both functions, while the original `PlainRectangle` keeps working with `rectangle_area` alone: - -```rust -#[derive(HasField)] -pub struct ScaledRectangle { - pub width: f64, - pub height: f64, - pub scale_factor: f64, -} - -let r = ScaledRectangle { width: 3.0, height: 4.0, scale_factor: 2.0 }; -assert_eq!(r.rectangle_area(), 12.0); -assert_eq!(r.scaled_rectangle_area(), 48.0); -``` - -## A unified interface across shapes - -A single `#[cgp_fn]` defines exactly one implementation, so it cannot serve as a common interface that different shapes implement differently. For that, define a [component](../concepts/consumer-and-provider-traits.md) with [`#[cgp_component]`](../reference/macros/cgp_component.md). The annotated `CanCalculateArea` trait is the *consumer trait* callers use; the `AreaCalculator` argument names the generated *provider trait* that implementations target: - -```rust -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} -``` - -Each implementation is a *named provider* written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md). Unlike a blanket `impl`, named providers may overlap freely — a rectangle calculator and a circle calculator can both exist even though a context could in principle have the fields for either. Implicit arguments work here just as in `#[cgp_fn]`, so the field-reading logic can be inlined directly: - -```rust -#[cgp_impl(new RectangleAreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} - -#[cgp_impl(new CircleAreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] radius: f64) -> f64 { - core::f64::consts::PI * radius * radius - } -} -``` - -## Wiring contexts to providers - -Defining a provider does not attach it to any context; a context chooses its provider by wiring with [`delegate_components!`](../reference/macros/delegate_components.md). Each entry maps the component — keyed by its generated `…Component` name — to the provider that should implement it for that context: - -```rust -#[derive(HasField)] -pub struct PlainCircle { - pub radius: f64, -} - -delegate_components! { - PlainRectangle { - AreaCalculatorComponent: RectangleAreaCalculator, - } -} - -delegate_components! { - PlainCircle { - AreaCalculatorComponent: CircleAreaCalculator, - } -} -``` - -With the wiring in place, the consumer-trait method is available on each context, dispatched to its chosen provider at compile time with no runtime indirection: - -```rust -assert_eq!(PlainRectangle { width: 2.0, height: 3.0 }.area(), 6.0); -assert_eq!(PlainCircle { radius: 4.0 }.area(), 16.0 * core::f64::consts::PI); -``` - -## Composing providers - -Scaling applies to every shape the same way, so writing a separate scaled provider per shape would duplicate the same logic. A [higher-order provider](../concepts/higher-order-providers.md) captures the transformation once and takes the base calculation as a provider parameter — `InnerCalculator`, declared as an impl generic. The [`#[use_provider]`](../reference/attributes/use_provider.md) attribute supplies that inner provider's bound, filling in the leading context argument a provider trait carries, while the body invokes it as an associated function: - -```rust -#[cgp_impl(new ScaledAreaCalculator)] -#[use_provider(InnerCalculator: AreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - InnerCalculator::area(self) * scale_factor * scale_factor - } -} -``` - -A context now selects its base calculator and its scaling in one wiring entry by nesting the providers. The same `ScaledAreaCalculator` composes over any inner calculator, so a scaled circle reuses exactly the scaling logic a scaled rectangle uses: - -```rust -#[derive(HasField)] -pub struct ScaledCircle { - pub radius: f64, - pub scale_factor: f64, -} - -delegate_components! { - ScaledRectangle { - AreaCalculatorComponent: ScaledAreaCalculator, - } -} - -delegate_components! { - ScaledCircle { - AreaCalculatorComponent: ScaledAreaCalculator, - } -} - -let r = ScaledRectangle { width: 3.0, height: 4.0, scale_factor: 2.0 }; -assert_eq!(r.area(), 48.0); - -let c = ScaledCircle { radius: 3.0, scale_factor: 2.0 }; -assert_eq!(c.area(), 36.0 * core::f64::consts::PI); -``` - -Because providers are plain type-level markers, a long composition can be given a shorter name with an ordinary type alias — `pub type ScaledRectangleAreaCalculator = ScaledAreaCalculator;` — and used anywhere the expanded form would be. - diff --git a/docs/examples/expression-interpreter.md b/docs/examples/expression-interpreter.md deleted file mode 100644 index 3eff91f1..00000000 --- a/docs/examples/expression-interpreter.md +++ /dev/null @@ -1,328 +0,0 @@ -# Expression interpreter - -This example builds a modular interpreter for a small arithmetic language, where each operator is its own type and each operation over the language — evaluation, conversion to Lisp — is a separate provider, so variants and operations can both be added without editing existing code. It progresses from the closed enum-and-`match` form, through per-variant evaluation providers wired by input dispatch, to a second operation, a generalized operator provider, code-based dispatch between operations, and finally an extended language with new variants. It is a template for any recursive data type — expression trees, JSON values, syntax trees — that must stay open to new cases and new traversals at once, the classic [expression problem](https://en.wikipedia.org/wiki/Expression_problem). - -The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: - -- handling each variant of an enum independently — [extensible variants](../concepts/extensible-variants.md) and the [extensible visitor pattern](../concepts/dispatching.md) -- exposing an enum as a sum of named variants — [`#[derive(HasFields)]`](../reference/derives/derive_has_fields.md), [`#[derive(FromVariant)]`](../reference/derives/derive_from_variant.md), [`#[derive(ExtractField)]`](../reference/derives/derive_extract_field.md) -- the computation components — [`Computer` / `CanCompute`](../reference/components/computer.md) and its by-reference variant `ComputerRef` -- writing a per-variant provider — [`#[cgp_impl]`](../reference/macros/cgp_impl.md) -- routing on the input variant and on the operation — [`UseDelegate`](../reference/providers/use_delegate.md) and [dispatching](../concepts/dispatching.md) -- the variant dispatcher — [`MatchWithValueHandlers`](../reference/providers/dispatch_combinators.md) -- constructing part of a target enum — [`CanUpcast`](../reference/traits/cast.md) -- abstract output types per context — [`#[cgp_type]`](../reference/macros/cgp_type.md) and [`UseType`](../reference/providers/use_type.md) - -All snippets assume `use cgp::prelude::*;`, with the computation items from `cgp::extra::handler`, the dispatchers from `cgp::extra::dispatch`, and `CanUpcast` from `cgp::core::field::impls`. - -## The closed interpreter - -The conventional way to model the language is one enum with a `match`-based function per operation: - -```rust -pub enum Expr { - Plus(Box, Box), - Times(Box, Box), - Literal(u64), -} - -pub fn eval(expr: Expr) -> u64 { - match expr { - Expr::Plus(a, b) => eval(*a) + eval(*b), - Expr::Times(a, b) => eval(*a) * eval(*b), - Expr::Literal(value) => value, - } -} -``` - -This is concise but closed in two directions. Adding a variant forces every function that matches on `Expr` to change, and the recursive structure means even a helper like `eval_plus` must still mention `Expr`. A real expression type such as `syn::Expr` has dozens of variants and many operations, so this coupling becomes the central obstacle the rest of the example removes. - -## Variants as standalone types - -The first move is to give each operator its own type, generic over the expression it nests, rather than burying its shape in the enum: - -```rust -#[derive(Debug, Eq, PartialEq, HasField)] -pub struct Plus { - pub left: Box, - pub right: Box, -} - -#[derive(Debug, Eq, PartialEq, HasField)] -pub struct Times { - pub left: Box, - pub right: Box, -} - -#[derive(Debug, Eq, PartialEq)] -pub struct Literal(pub T); -``` - -Each operator is now a reusable building block parameterized by the broader expression type, which is what lets the same `Plus` appear in several languages. `Plus` and `Times` derive [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) so their `left` and `right` fields can be read generically later. - -## Evaluating one variant - -Evaluation is a [`Computer`](../reference/components/computer.md) — CGP's component for a synchronous, pure computation whose consumer trait is `CanCompute`. One provider handles addition, recursing into the operands through the context's own evaluation: - -```rust -#[cgp_impl(new EvalAdd)] -impl Computer> -where - Self: CanCompute, - Output: Add, -{ - type Output = Output; - - fn compute(&self, code: PhantomData, Plus { left, right }: Plus) -> Self::Output { - let output_a = self.compute(code, *left); - let output_b = self.compute(code, *right); - output_a + output_b - } -} -``` - -`EvalAdd` is written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) and is completely decoupled: it knows nothing of the concrete expression enum, only that the context can evaluate a nested `MathExpr` to some `Output` that supports `Add`. Multiplication is identical with `Mul`, and the literal case is the base of the recursion — it just returns its inner value, needing nothing from the context: - -```rust -#[cgp_impl(new EvalLiteral)] -impl Computer> { - type Output = T; - - fn compute(&self, _code: PhantomData, Literal(value): Literal) -> T { - value - } -} -``` - -Each provider lives on its own and could be defined in a separate crate; nothing ties `EvalAdd`, `EvalMultiply`, and `EvalLiteral` together until a context composes them. - -## Assembling the evaluator - -The concrete enum wraps the standalone operator types and derives the [extensible-variant](../concepts/extensible-variants.md) machinery so it can be taken apart generically: - -```rust -pub type Value = u64; - -#[derive(Debug, HasFields, FromVariant, ExtractField)] -pub enum MathExpr { - Plus(Plus), - Times(Times), - Literal(Literal), -} -``` - -The context is an empty struct whose only job is to wire each input type to its provider. [`UseInputDelegate`](../reference/providers/use_delegate.md) keys the `Computer` lookup on the *input* type, so `Plus` routes to `EvalAdd`, `Literal` to `EvalLiteral`, and the whole enum to a dispatcher: - -```rust -pub struct Interpreter; - -delegate_components! { - Interpreter { - ComputerComponent: - UseInputDelegate: EvalAdd, - Times: EvalMultiply, - Literal: EvalLiteral, - }>, - } -} - -#[cgp_impl(new DispatchEval)] -impl Computer for Interpreter { - type Output = Value; - - fn compute(context: &Interpreter, code: PhantomData, expr: MathExpr) -> Self::Output { - ::compute(context, code, expr) - } -} -``` - -The whole `MathExpr` enum is handled by `DispatchEval`, a context-specific provider that defers to [`MatchWithValueHandlers`](../reference/providers/dispatch_combinators.md) — the variant dispatcher that derives one handler per variant from the enum's own variant list and runs them as a match, described in [dispatching](../concepts/dispatching.md). The thin `DispatchEval` wrapper is needed to break a trait-resolution cycle: wiring `MatchWithValueHandlers` directly for `MathExpr` would require the compiler to resolve the per-variant providers, which themselves route back through the dispatcher. Marking the trait implemented in the wrapper's body breaks the cycle. - -## A second operation over the same language - -Converting an expression to a Lisp [S-expression](https://en.wikipedia.org/wiki/S-expression) is a second operation, and adding it must not touch the evaluator. It uses `ComputerRef`, the by-reference variant of `Computer`, so the expression can still be used afterward, and it targets a separate `LispExpr` enum — making this a "double" expression problem, decoupled from both the source and the target type. The target type stays abstract through a [`#[cgp_type]`](../reference/macros/cgp_type.md) component: - -```rust -#[cgp_type] -pub trait HasLispExprType { - type LispExpr; -} - -#[cgp_impl(new PlusToLisp)] -impl ComputerRef> -where - Self: HasLispExprType + CanComputeRef, - LispSubExpr: CanUpcast, -{ - type Output = LispExpr; - - fn compute_ref(&self, code: PhantomData, Plus { left, right }: &Plus) -> Self::Output { - let expr_a = self.compute_ref(code, left); - let expr_b = self.compute_ref(code, right); - let ident = LispSubExpr::Ident(Ident("+".to_owned())).upcast(PhantomData); - - LispSubExpr::List(List(vec![ident.into(), expr_a.into(), expr_b.into()])).upcast(PhantomData) - } -} -``` - -`PlusToLisp` only needs to build two kinds of `LispExpr` — a list and an identifier — so rather than depend on the full target enum it defines a small local enum with just those variants and [upcasts](../reference/traits/cast.md) into the full `LispExpr`: - -```rust -#[derive(HasFields, ExtractField, FromVariant)] -enum LispSubExpr { - List(List), - Ident(Ident), -} -``` - -This is the variant-side analog of reading only the fields you need from a struct: `CanUpcast` constructs the parts of an enum a provider cares about without binding it to the entire definition. Wiring the new operation adds a `ComputerRefComponent` table and a `DispatchToLisp` wrapper alongside the existing evaluator, and binds the abstract `LispExpr` type to the concrete enum with [`UseType`](../reference/providers/use_type.md): - -```rust -delegate_components! { - Interpreter { - MathExprTypeProviderComponent: UseType, - LispExprTypeProviderComponent: UseType, - ComputerComponent: - UseInputDelegate: EvalAdd, - Times: EvalMultiply, - Literal: EvalLiteral, - }>, - ComputerRefComponent: - UseInputDelegate: LiteralToLisp, - Plus: PlusToLisp, - Times: TimesToLisp, - }>, - } -} -``` - -The evaluator wiring is untouched; the conversion is added purely by extension. - -## One provider for every binary operator - -`PlusToLisp` and `TimesToLisp` differ only in the operator symbol, so they collapse into a single provider parameterized by the operator. The operator is a type-level string, and the operand fields are read through a getter that any binary struct satisfies: - -```rust -#[cgp_auto_getter] -pub trait BinarySubExpression { - fn left(&self) -> &Box; - fn right(&self) -> &Box; -} - -#[cgp_impl(new BinaryOpToLisp)] -impl ComputerRef -where - Self: HasMathExprType - + HasLispExprType - + CanComputeRef, - MathSubExpr: BinarySubExpression, - Operator: Default + Display, - LispSubExpr: CanUpcast, -{ - type Output = LispExpr; - - fn compute_ref(&self, code: PhantomData, expr: &MathSubExpr) -> Self::Output { - let expr_a = self.compute_ref(code, expr.left()); - let expr_b = self.compute_ref(code, expr.right()); - let ident = LispSubExpr::Ident(Ident(Operator::default().to_string())).upcast(PhantomData); - - LispSubExpr::List(List(vec![ident.into(), expr_a.into(), expr_b.into()])).upcast(PhantomData) - } -} -``` - -`BinaryOpToLisp` works for any `MathSubExpr` whose `left` and `right` fields the [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) trait `BinarySubExpression` can read — which is why `Plus` and `Times` derived `HasField` earlier. Both operators now wire to the same provider with a different [`Symbol!`](../reference/macros/symbol.md) operator string: - -```rust -ComputerRefComponent: - UseInputDelegate: LiteralToLisp, - Plus: BinaryOpToLisp, - Times: BinaryOpToLisp, - }>, -``` - -## Dispatching on the operation as well as the input - -Evaluation and conversion can share one component by adding a second layer of dispatch keyed on the *operation*. Marker types name the operations, and a per-variant provider routes on them with [`UseDelegate`](../reference/providers/use_delegate.md): - -```rust -pub struct Eval; -pub struct ToLisp; - -delegate_components! { - new HandlePlus { - ComputerRefComponent: UseDelegate, - }> - } -} -``` - -`HandlePlus` interprets a `Plus` as either evaluation or conversion depending on the `Code`, and `HandleTimes`, `HandleLiteral`, and `HandleMathExpr` follow the same shape. The context then dispatches first on the input type and second on the operation — a two-layer table whose order is a free choice that costs nothing at runtime, since all of it resolves through trait selection at compile time. Defining the operation routing through `delegate_components!` rather than separate `impl` blocks is what keeps the `Eval` and `ToLisp` logic free to live in different crates. - -## Extending the language - -A new language lives alongside the old one rather than replacing it, which is the whole point of keeping variants standalone. Subtraction and negation get their own types and evaluation providers, written exactly like the originals: - -```rust -pub struct Minus { pub left: Box, pub right: Box } -pub struct Negate(pub Box); - -#[cgp_impl(new EvalSubtract)] -impl ComputerRef> -where - Self: CanComputeRef, - Output: Sub, -{ - type Output = Output; - - fn compute_ref(&self, code: PhantomData, Minus { left, right }: &Minus) -> Self::Output { - let output_a = self.compute_ref(code, left); - let output_b = self.compute_ref(code, right); - output_a - output_b - } -} -``` - -The extended enum reuses the original operator providers, now instantiated at a signed `Value` so negation has something to return: - -```rust -pub type Value = i64; - -#[derive(Debug, HasFields, FromVariant, ExtractField)] -pub enum MathPlusExpr { - Plus(Plus), - Times(Times), - Literal(Literal), - Negate(Negate), - Minus(Minus), -} - -delegate_components! { - InterpreterPlus { - ComputerRefComponent: - UseDelegate: EvalAdd, - Times: EvalMultiply, - Literal: EvalLiteral, - Minus: EvalSubtract, - Negate: EvalNegate, - }>, - }> - } -} -``` - -`EvalAdd` and `EvalMultiply` work unchanged because `i64` implements `Add` and `Mul` just as `u64` did — the providers never named a concrete numeric type. Equally telling is what is *absent*: `InterpreterPlus` wires only evaluation and simply omits a to-Lisp handler for `Minus` and `Negate`. Because CGP wiring is lazy and checked only where it is used, the evaluator compiles and runs without those handlers, so a new variant can be prototyped against one operation before the others catch up — the kind of partial extension a closed `enum` with exhaustive `match`es cannot express. diff --git a/docs/examples/extensible-shapes.md b/docs/examples/extensible-shapes.md deleted file mode 100644 index 4e1f1853..00000000 --- a/docs/examples/extensible-shapes.md +++ /dev/null @@ -1,226 +0,0 @@ -# Extensible shapes - -This example computes properties of geometric shapes — area, scaling — modeled as the variants of an enum, dispatching each operation to a per-variant implementation without a hand-written `match`. It progresses from a per-type operation that is lifted onto an enum automatically, through mutating and argument-taking operations, to converting between related shape enums and finally wiring the dispatch into a context. It is a template for any use case where one operation has a separate implementation per case of a sum type, and the set of cases should stay open to extension — the *extensible visitor* counterpart to the per-context dispatch in [area calculation](area-calculation.md). - -The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: - -- shapes as the variants of an enum — [extensible variants](../concepts/extensible-variants.md) via [`#[derive(CgpData)]`](../reference/derives/derive_cgp_data.md) -- dispatching an operation to per-variant implementations — [`#[cgp_auto_dispatch]`](../reference/macros/cgp_auto_dispatch.md) -- per-variant handlers as computers — [`#[cgp_computer]`](../reference/macros/cgp_computer.md) producing [`Computer`](../reference/components/computer.md) providers -- widening and narrowing the variant set — [upcasting and downcasting](../reference/traits/cast.md) -- the dispatch machinery underneath — the [dispatch combinators](../reference/providers/dispatch_combinators.md) and [dispatching](../concepts/dispatching.md) -- wiring dispatch into a context — [`delegate_components!`](../reference/macros/delegate_components.md) with [`UseInputDelegate`](../reference/providers/use_delegate.md) and a [`check_components!`](../reference/macros/check_components.md) assertion - -All snippets assume `use cgp::prelude::*;`; the dispatch combinators come from `cgp::extra::dispatch`, `UseInputDelegate` from `cgp::extra::handler`, and the cast helpers from `cgp::core::field::impls`. Each shape is its own payload struct: - -```rust -#[derive(Debug, PartialEq)] -pub struct Circle { - pub radius: f64, -} - -#[derive(Debug, PartialEq)] -pub struct Rectangle { - pub width: f64, - pub height: f64, -} - -#[derive(Debug, PartialEq)] -pub struct Triangle { - pub base: f64, - pub height: f64, -} -``` - -## A per-type operation, lifted onto enums - -The smallest unit is an ordinary trait with one implementation per shape type. Marking the trait with [`#[cgp_auto_dispatch]`](../reference/macros/cgp_auto_dispatch.md) is what later lets any *enum* of these shapes gain the operation for free, dispatched to the matching variant — but the per-type impls themselves are plain Rust: - -```rust -#[cgp_auto_dispatch] -pub trait HasArea { - fn area(self) -> f64; -} - -impl HasArea for Circle { - fn area(self) -> f64 { - core::f64::consts::PI * self.radius * self.radius - } -} - -impl HasArea for Rectangle { - fn area(self) -> f64 { - self.width * self.height - } -} - -impl HasArea for Triangle { - fn area(self) -> f64 { - self.base * self.height / 2.0 - } -} -``` - -`#[cgp_auto_dispatch]` leaves the trait and these impls untouched and generates, behind the scenes, the per-variant handler and the enum-level implementation that runs it. The operation reads like a normal method; the dispatch is what the macro supplies. - -## An enum of shapes - -A concrete shape is one enum over the payload types, made [extensible](../concepts/extensible-variants.md) with [`#[derive(CgpData)]`](../reference/derives/derive_cgp_data.md) so it can be taken apart by the variant name generically. Each variant holds exactly one payload — the single-field tuple form the derive requires: - -```rust -#[derive(Debug, PartialEq, CgpData)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} -``` - -Because `HasArea` carries `#[cgp_auto_dispatch]` and `Shape` is extensible, `Shape` now implements `HasArea` too, matching its current variant and delegating to that payload's `area`: - -```rust -let shape = Shape::Circle(Circle { radius: 5.0 }); -assert_eq!(shape.area(), 25.0 * core::f64::consts::PI); -``` - -The dispatch is resolved at compile time, and forgetting an `impl HasArea` for one variant's payload is a compile error at the point `Shape::area` is used, not a runtime fallthrough. - -## Operations that mutate or take arguments - -The same dispatch covers every method shape, not just a by-value reader. A `&mut self` method that also takes an argument — scaling a shape by a factor — dispatches identically; `#[cgp_auto_dispatch]` selects the matcher form that matches the receiver and threads the extra argument through to each variant's implementation: - -```rust -#[cgp_auto_dispatch] -pub trait CanScale { - fn scale(&mut self, factor: f64); -} - -impl CanScale for Circle { - fn scale(&mut self, factor: f64) { - self.radius *= factor; - } -} - -impl CanScale for Rectangle { - fn scale(&mut self, factor: f64) { - self.width *= factor; - self.height *= factor; - } -} - -impl CanScale for Triangle { - fn scale(&mut self, factor: f64) { - self.base *= factor; - self.height *= factor; - } -} - -let mut shape = Shape::Rectangle(Rectangle { width: 2.0, height: 2.0 }); -shape.scale(2.0); -assert_eq!(shape.area(), 16.0); -``` - -A trait may mix reading and mutating methods, by-value and by-reference receivers, extra arguments, and `async` methods, all dispatched the same way; the [`#[cgp_auto_dispatch]`](../reference/macros/cgp_auto_dispatch.md) reference covers which matcher each shape selects. - -## Widening and narrowing the shape set - -Two enums that share variants interconvert with no hand-written conversion, through the [casting traits](../reference/traits/cast.md). A wider `ShapePlus` adds a `Triangle` case alongside the two `Shape` already has: - -```rust -#[derive(Debug, PartialEq, CgpData)] -pub enum ShapePlus { - Triangle(Triangle), - Rectangle(Rectangle), - Circle(Circle), -} -``` - -Upcasting a `Shape` into `ShapePlus` always succeeds, since every `Shape` variant has a home in the wider enum; downcasting back may fail, returning the value untouched when its variant has no home in the narrower target: - -```rust -let shape = Shape::Circle(Circle { radius: 5.0 }); -let wider: ShapePlus = shape.upcast(PhantomData::); -assert_eq!(wider, ShapePlus::Circle(Circle { radius: 5.0 })); - -let narrowed = ShapePlus::Circle(Circle { radius: 5.0 }).downcast(PhantomData::); -assert_eq!(narrowed.ok(), Some(Shape::Circle(Circle { radius: 5.0 }))); -``` - -A `ShapePlus::Triangle` cannot narrow to `Shape`, so its `downcast` returns `Err(remainder)`. The remainder is the partial enum with the already-tried variants ruled out, and it can be narrowed again against another candidate with `downcast_fields` — narrowing a leftover `Triangle` into a `TriangleOnly` enum, which here cannot fail: - -```rust -#[derive(Debug, PartialEq, CgpData)] -pub enum TriangleOnly { - Triangle(Triangle), -} -``` - -## Under the hood: the dispatch combinators - -What `#[cgp_auto_dispatch]` writes for `Shape::area` is a [dispatch combinator](../reference/providers/dispatch_combinators.md), and the same dispatch can be assembled by hand. The macro turns each method into a per-variant [`Computer`](../reference/components/computer.md) provider with [`#[cgp_computer]`](../reference/macros/cgp_computer.md) — `area` yields a provider named `ComputeArea` — and then runs a *matcher* over the enum's variants: - -```rust -use cgp::extra::dispatch::{ExtractFieldAndHandle, HandleFieldValue, MatchWithHandlers}; - -let circle = Shape::Circle(Circle { radius: 5.0 }); - -let area = MatchWithHandlers::>, - ExtractFieldAndHandle>, -]>::compute(&(), PhantomData::<()>, circle); -``` - -Each `ExtractFieldAndHandle` tries one variant by its [`Symbol!`](../reference/macros/symbol.md) name and, on a match, hands the payload to `ComputeArea`; the list runs first-match-wins, and once every variant has been tried the remainder is uninhabited, so the match is provably exhaustive with no wildcard arm. The `ComputeArea` provider is what [`#[cgp_computer]`](../reference/macros/cgp_computer.md) produces from a function that forwards to the trait method — equivalent to `fn compute_area(s: S) -> f64 { s.area() }` — and it is exactly the per-variant handler `#[cgp_auto_dispatch]` generates. - -Spelling out one adapter per variant is mechanical, so `MatchWithValueHandlers` is the shorthand that derives the same list from the enum's own variant list; naming the per-variant provider once dispatches the whole enum: - -```rust -let circle = Shape::Circle(Circle { radius: 5.0 }); -let _area = MatchWithValueHandlers::::compute(&(), PhantomData::<()>, circle); -``` - -The matcher is a `Computer` provider invoked with a unit context `&()`, because the per-variant logic depends only on the payload — and this `MatchWithValueHandlers::` call is the very body `#[cgp_auto_dispatch]` writes into the enum's generated `area` method. - -## Wiring dispatch into a context - -Rather than implement the operation directly on the enum, the dispatch can be wired into a context's [`Computer`](../reference/components/computer.md) component, keyed on the *input* type with [`UseInputDelegate`](../reference/providers/use_delegate.md). A bare payload type routes straight to its computer, while an enum routes to the matcher, which dispatches each extracted payload back through the context's own wiring: - -```rust -use cgp::extra::handler::UseInputDelegate; - -pub struct App; - -delegate_components! { - App { - ComputerComponent: UseInputDelegate, - } -} -``` - -A `Circle` input resolves to `ComputeArea` directly. A `Shape` or `ShapePlus` input resolves to `MatchWithValueHandlers` — written here with no provider argument, so it defaults to [`UseContext`](../reference/providers/use_context.md) and dispatches each extracted payload *back through* `App`'s own `ComputerComponent` rather than to a fixed handler. That is why the payload types share the table: the matcher routes a `Circle` payload to whatever `App` wires for `Circle`, which here is `ComputeArea`. - -Routing through the context is what makes individual variants overridable. Swapping the handler for one shape — wiring `Circle` to an optimized provider, say — is a one-line change to the table that leaves the matcher and every other variant untouched, precisely because the matcher never names `ComputeArea` itself. Pinning the matcher to a concrete provider as `MatchWithValueHandlers` is the other option, used when the dispatch should bypass the context entirely, as in the unit-context calls earlier. - -Because shape handlers are leaves — `ComputeArea` never calls back into the dispatcher — the enums can wire `MatchWithValueHandlers` directly. A *recursive* visitor cannot: the [expression interpreter](expression-interpreter.md) routes its enum through a thin wrapper provider instead, to break the trait-resolution cycle that its self-recursive handlers would otherwise create. Because CGP wiring is [checked lazily](../concepts/check-traits.md), a [`check_components!`](../reference/macros/check_components.md) block asserts at compile time that both enums are fully dispatchable, listing each as a `(Code, Input)` pair for the generic `Computer` component: - -```rust -check_components! { - App { - ComputerComponent: [ - ((), Shape), - ((), ShapePlus), - ], - } -} -``` diff --git a/docs/examples/modular-serialization.md b/docs/examples/modular-serialization.md deleted file mode 100644 index 5269e92c..00000000 --- a/docs/examples/modular-serialization.md +++ /dev/null @@ -1,403 +0,0 @@ -# Modular serialization - -This example rebuilds Serde's `Serialize` and `Deserialize` as CGP components, so that how each value type is encoded becomes a per-context wiring choice rather than a single fixed implementation baked into the type. It progresses from the two serialization components, through a family of overlapping providers that vanilla Rust would reject, to two application contexts that serialize the same nested data into different JSON formats by changing only a handful of wiring lines, and finally to a context-dependent deserializer that allocates into an arena. It is the template for any capability where the same type needs several interchangeable implementations selected per application, and where the orphan rule would otherwise force a library to derive the trait on every data type itself. - -The concepts each step demonstrates are documented in full in the reference; this example notes which one is in play and links to it: - -- splitting a trait so overlapping and orphan implementations are legal — [consumer and provider traits](../concepts/consumer-and-provider-traits.md) and the [coherence](../concepts/coherence.md) strategy behind it -- defining the components — [`#[cgp_component]`](../reference/macros/cgp_component.md) -- writing the providers — [`#[cgp_impl]`](../reference/macros/cgp_impl.md), with [`#[uses]`](../reference/attributes/uses.md) for the capabilities they look up through the context -- serializing a struct with no serialization-specific derive — [extensible records](../concepts/extensible-records.md) via [`#[derive(CgpData)]`](../reference/derives/derive_cgp_data.md) -- selecting a provider per value type, inline in the context's own table — the `open` statement of [`delegate_components!`](../reference/macros/delegate_components.md) with [`@`-path keys](../concepts/namespaces.md) -- verifying a context's wiring — [`check_components!`](../reference/macros/check_components.md) -- pulling a capability from the context during deserialization — [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) over [`HasField`](../reference/traits/has_field.md), with the [`HasErrorType`](../reference/components/has_error_type.md) and [`CanRaiseError`](../reference/components/can_raise_error.md) error components wired through [modular error handling](../concepts/modular-error-handling.md) - -All snippets assume `use cgp::prelude::*;` and use Serde's `Serializer`/`Deserializer` traits directly. The providers shown here serialize and deserialize, but the example builds up the serialization side first and then mirrors it for deserialization. - -## The two serialization components - -The starting point is a context-generic restatement of Serde's two traits. Each moves the type being serialized out of the `Self` position — where Serde keeps it — and into an explicit `Value` parameter, leaving `Self` to name a **context** that carries the wiring: - -```rust -#[cgp_component(ValueSerializer)] -pub trait CanSerializeValue { - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer; -} - -#[cgp_component(ValueDeserializer)] -pub trait CanDeserializeValue<'de, Value> { - fn deserialize(&self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>; -} -``` - -`CanSerializeValue` and `CanDeserializeValue` are the [consumer traits](../concepts/consumer-and-provider-traits.md) callers use as `context.serialize(value, s)`; `ValueSerializer` and `ValueDeserializer` are the provider traits implementations are written against. The extra `&self` is the whole point — it gives every implementation access to the context, both to look up how to serialize nested values and, for deserialization, to pull runtime dependencies out of it. Because `Value` is a generic parameter rather than the `Self` type, a context can later wire a different provider for each concrete value type, which is the per-type dispatch set up when the contexts are wired below. - -## Overlapping providers - -With the type moved off `Self`, several implementations of the same component can coexist even though they overlap — each is written for its own zero-sized provider struct, which the defining crate owns, so the [coherence](../concepts/coherence.md) rules never apply. The simplest provider stays compatible with the existing Serde ecosystem by deferring to Serde's own `Serialize`: - -```rust -pub struct UseSerde; - -#[cgp_impl(UseSerde)] -impl ValueSerializer -where - Value: serde::Serialize, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer, - { - value.serialize(serializer) - } -} -``` - -`#[cgp_impl(UseSerde)]` reads like a blanket impl of the consumer trait, but the provider name in the attribute is what becomes the actual `Self` type, so this works for *any* `Context` and *any* `Value: Serialize` without overlapping anything. A second provider serializes any byte container directly as bytes, overlapping `UseSerde` on every type that is both `Serialize` and `AsRef<[u8]>` — `Vec` among them: - -```rust -pub struct SerializeBytes; - -#[cgp_impl(SerializeBytes)] -impl ValueSerializer -where - Value: AsRef<[u8]>, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_bytes(value.as_ref()) - } -} -``` - -In vanilla Rust these two blanket implementations could not both exist; as named providers they are simply two entries a context may choose between. Which one a given context uses for `Vec` is decided entirely by its wiring, shown further below. - -## Looking serialization up through the context - -A provider needs more than its own logic when it serializes by delegating to another encoding — and it gets that by asking the context. `SerializeWithDisplay` formats any `Display` value to a string and then serializes *that string through the context*, so the eventual byte-level representation of the string is itself a wiring choice rather than fixed here. The capability it depends on is declared with [`#[uses]`](../reference/attributes/uses.md), which adds the bound `Self: CanSerializeValue` as an [impl-side dependency](../concepts/impl-side-dependencies.md): - -```rust -#[cgp_impl(new SerializeWithDisplay)] -#[uses(CanSerializeValue)] -impl ValueSerializer -where - Value: core::fmt::Display, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer, - { - let str_value = value.to_string(); - self.serialize(&str_value, serializer) - } -} -``` - -The `new` keyword defines the `SerializeWithDisplay` struct in passing. The same shape encodes a byte container as a hexadecimal string — converting to a `String` and handing it back to the context — which is one of the two formats the demo needs: - -```rust -pub struct SerializeHex; - -#[cgp_impl(SerializeHex)] -#[uses(CanSerializeValue)] -impl ValueSerializer -where - Value: hex::ToHex, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer, - { - let str_value = value.encode_hex::(); - self.serialize(&str_value, serializer) - } -} -``` - -A base64 provider is identical but for the encoding call, and a date provider serializes a `DateTime` by formatting it to an RFC 3339 string and serializing that through the context, while an alternative serializes the same `DateTime` as a Unix timestamp `i64`. Each is a separate provider overlapping the others on its value type, and the context picks one. - -## Serializing collections and structs - -Two recursive providers handle composite values by serializing each part through the context, so customization reaches arbitrarily deep without any provider knowing the concrete shape. `SerializeIterator` serializes any iterable as a sequence, asking the context how to serialize each item: - -```rust -#[cgp_impl(new SerializeIterator)] -impl ValueSerializer -where - for<'a> &'a Value: IntoIterator, - Self: for<'a> CanSerializeValue<<&'a Value as IntoIterator>::Item>, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut seq = serializer.serialize_seq(None)?; - for item in value.into_iter() { - seq.serialize_element(&SerializeWithContext::new(self, &item))?; - } - seq.end() - } -} -``` - -The item bound is a higher-ranked `Self: for<'a> CanSerializeValue<...>` rather than a `#[uses]` line, because a higher-ranked bound is beyond what the simplified `#[uses]` syntax expresses. `SerializeWithContext` is the adapter that pairs a value with a context and implements Serde's own `Serialize`, which is how a nested value re-enters the context's wiring — it appears again at the top level below. The companion `SerializeFields` serializes any struct as a map by walking its fields, available because the struct derives [`CgpData`](../reference/derives/derive_cgp_data.md) and so exposes its fields through [`HasFields`](../reference/traits/has_fields.md): - -```rust -#[cgp_impl(new SerializeFields)] -impl ValueSerializer -where - Value: HasFields, - Value::Fields: FieldsSerializer, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer, - { - let map = serializer.serialize_map(None)?; - Value::Fields::serialize_fields(self, value, map) - } -} -``` - -This is the payoff for the orphan rule: a data type needs no serialization-specific derive and no dependency on this crate at all. Deriving the general-purpose `CgpData` is enough for `SerializeFields` to serialize it generically — see [extensible records](../concepts/extensible-records.md) for what that derive exposes — so a library never has to implement a serialization trait on types it owns just because a downstream application wants to encode them. - -## The data types - -The demo serializes a small tree of encrypted-messaging types, each carrying a byte field or a `DateTime` whose encoding the applications will want to control. Their only derive is `CgpData`: - -```rust -use chrono::{DateTime, Utc}; - -#[derive(CgpData)] -pub struct EncryptedMessage { - pub message_id: u64, - pub author_id: u64, - pub date: DateTime, - pub encrypted_data: Vec, -} - -#[derive(CgpData)] -pub struct MessagesByTopic { - pub encrypted_topic: Vec, - pub messages: Vec, -} - -#[derive(CgpData)] -pub struct MessagesArchive { - pub decryption_key: Vec, - pub messages_by_topics: Vec, -} -``` - -## Wiring an application context - -A context turns this pile of overlapping providers into one coherent scheme by choosing, per value type, which provider runs. The `open` statement in [`delegate_components!`](../reference/macros/delegate_components.md) opens the serialization component for per-type wiring directly in the context's own table; after it, an `@ValueSerializerComponent.: ` entry assigns a provider to each value type the archive touches, the type written as a [`@`-path key](../concepts/namespaces.md). `open` is the lightweight wiring form that suits a self-contained application like this one; a large code base with many components instead shares wiring through named [namespaces](../concepts/namespaces.md) that contexts join and selectively override: - -```rust -pub struct AppA; - -delegate_components! { - AppA { - open ValueSerializerComponent; - - @ValueSerializerComponent.<'a, T> &'a T: - SerializeDeref, - @ValueSerializerComponent.[ - u64, - String, - ]: - UseSerde, - @ValueSerializerComponent.Vec: - SerializeHex, - @ValueSerializerComponent.DateTime: - SerializeRfc3339Date, - @ValueSerializerComponent.[ - Vec, - Vec, - ]: - SerializeIterator, - @ValueSerializerComponent.[ - MessagesArchive, - MessagesByTopic, - EncryptedMessage, - ]: - SerializeFields, - } -} -``` - -Reading the table top to bottom: a borrowed value routes to `SerializeDeref` (which forwards to the value behind the reference, encountered while serializing nested items), the scalar types fall back to plain Serde, `Vec` is encoded as hexadecimal, `DateTime` as an RFC 3339 string, the collections as sequences, and the structs as maps. Because the byte and date entries are the only ones that fix a *format*, a second application differs in only a few lines — base64 instead of hex, Unix timestamps instead of RFC 3339, plus an `i64` entry for the timestamps: - -```rust -pub struct AppB; - -delegate_components! { - AppB { - open ValueSerializerComponent; - - @ValueSerializerComponent.<'a, T> &'a T: - SerializeDeref, - @ValueSerializerComponent.[ - i64, - u64, - String, - ]: - UseSerde, - @ValueSerializerComponent.Vec: - SerializeBase64, - @ValueSerializerComponent.DateTime: - SerializeTimestamp, - @ValueSerializerComponent.[ - Vec, - Vec, - ]: - SerializeIterator, - @ValueSerializerComponent.[ - MessagesArchive, - MessagesByTopic, - EncryptedMessage, - ]: - SerializeFields, - } -} -``` - -The two contexts resolve `Vec` to overlapping providers — `SerializeHex` and `SerializeBase64` — with no conflict, because each choice is coherent only within its own context. CGP wiring is [checked lazily](../concepts/check-traits.md), so a [`check_components!`](../reference/macros/check_components.md) block asserts at compile time that each context can actually serialize every value type, listing them as the `Value` parameters of `ValueSerializerComponent`: - -```rust -check_components! { - AppA { - ValueSerializerComponent: [ - u64, - String, - Vec, - DateTime, - EncryptedMessage, - MessagesByTopic, - MessagesArchive, - ], - } -} -``` - -## Producing JSON - -Because the providers ultimately defer to a real `serde::Serializer`, the existing JSON ecosystem still does the writing. The bridge is `SerializeWithContext`, which wraps a context and a value into a type that implements Serde's `Serialize` by calling the context's `CanSerializeValue`: - -```rust -let archive = MessagesArchive { /* ... */ }; - -let json_a = serde_json::to_string_pretty(&SerializeWithContext::new(&AppA, &archive)).unwrap(); -let json_b = serde_json::to_string_pretty(&SerializeWithContext::new(&AppB, &archive)).unwrap(); -``` - -`json_a` encodes every byte field as hexadecimal and every date as an RFC 3339 string; `json_b` encodes the same archive with base64 and Unix timestamps. Nothing in the data types or the providers changed between the two — only which context wraps the value. - -## Deserializing with a context-supplied capability - -Deserialization mirrors serialization, and the extra `&self` becomes load-bearing in a way Serde cannot match: the context can supply runtime *capabilities* a provider pulls in by dependency injection. The motivating case is an [arena allocator](https://en.wikipedia.org/wiki/Region-based_memory_management) — deserializing many borrowed `&'a T` values into one arena instead of heap-allocating each. The context exposes the arena through a getter trait, written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) so any context with a matching field implements it automatically: - -```rust -use typed_arena::Arena; - -#[cgp_auto_getter] -pub trait HasArena<'a, T: 'a> { - fn arena(&self) -> &&'a Arena; -} -``` - -The provider for the borrowed type then deserializes the owned value through the context and moves it into the arena fetched from the context. Both capabilities it needs — the arena and the ability to deserialize the owned `Value` — are declared with [`#[uses]`](../reference/attributes/uses.md): - -```rust -#[cgp_impl(new DeserializeAndAllocate)] -#[uses(HasArena<'a, Value>, CanDeserializeValue<'de, Value>)] -impl<'de, 'a, Value> ValueDeserializer<'de, &'a Value> { - fn deserialize(&self, deserializer: D) -> Result<&'a Value, D::Error> - where - D: serde::Deserializer<'de>, - { - let value = self.deserialize(deserializer)?; - let value = self.arena().alloc(value); - Ok(value) - } -} -``` - -The data and the context complete the picture. The structs derive `CgpData` for generic field-by-field deserialization, and the context carries the arena as an ordinary field, deriving [`HasField`](../reference/traits/has_field.md) so `HasArena` is satisfied: - -```rust -#[derive(CgpData)] -pub struct Coord { - pub x: u64, - pub y: u64, - pub z: u64, -} - -#[derive(CgpData)] -pub struct Cluster<'a> { - pub id: u64, - pub coords: Vec<&'a Coord>, -} - -#[derive(HasField)] -pub struct App<'a> { - pub arena: &'a Arena, -} -``` - -The wiring opens the deserialization component and keys on the value type as before, routing the bare `Coord` and `Cluster` to a record-field deserializer, the borrowed `&'a Coord` to the arena allocator, and the `Vec<&'a Coord>` to a sequence deserializer. Deserialization can fail, so the context also wires the [`HasErrorType`](../reference/components/has_error_type.md) and [`CanRaiseError`](../reference/components/can_raise_error.md) error components to an `anyhow`-backed backend — those are ordinary component entries, written without `open` because they are not dispatched per value type: - -```rust -use cgp::core::error::{ErrorRaiserComponent, ErrorTypeProviderComponent}; -use cgp_error_anyhow::{RaiseAnyhowError, UseAnyhowError}; - -delegate_components! { - <'s> App<'s> { - open ValueDeserializerComponent; - - @ValueDeserializerComponent.u64: - UseSerde, - @ValueDeserializerComponent.[ - Coord, - <'a> Cluster<'a>, - ]: - DeserializeRecordFields, - @ValueDeserializerComponent.<'a> &'a Coord: - DeserializeAndAllocate, - @ValueDeserializerComponent.<'a> Vec<&'a Coord>: - DeserializeExtend, - - ErrorTypeProviderComponent: - UseAnyhowError, - ErrorRaiserComponent: - RaiseAnyhowError, - } -} -``` - -With the context built around an arena, deserializing a JSON cluster allocates its coordinates into that arena, and the returned `Cluster` borrows from it: - -```rust -let serialized = r#" - { - "id": 8, - "coords": [ - { "x": 1, "y": 2, "z": 3 }, - { "x": 4, "y": 5, "z": 6 } - ] - } -"#; - -let arena = Arena::new(); -let app = App { arena: &arena }; - -let cluster: Cluster<'_> = app.deserialize_json_string(serialized).unwrap(); -``` - -The arena was never an argument to a deserialize function — Serde's `from_str` has no slot for one. It reached `DeserializeAndAllocate` through the context, which is how CGP supplies a capability to code nested arbitrarily deep without threading it explicitly, the [dependency-injection](../concepts/impl-side-dependencies.md) idea applied to deserialization. diff --git a/docs/examples/money-transfer-api.md b/docs/examples/money-transfer-api.md deleted file mode 100644 index 81b328e4..00000000 --- a/docs/examples/money-transfer-api.md +++ /dev/null @@ -1,450 +0,0 @@ -# Money-transfer API - -This example builds the backend for a small money-transfer web service — querying a user's balance and moving funds between accounts — as a set of composable API handlers that an HTTP server drives. It progresses from abstract domain types and a status-coded error component, through a per-endpoint-dispatched handler and the reusable wrappers that add decoding, authentication, and encoding, to an in-memory context whose whole wiring is organized into a namespace and served over HTTP. It is a template for any request/response service whose endpoints share cross-cutting concerns and whose backend should be swappable behind abstract types. - -The concepts each step demonstrates are documented in full elsewhere; this example only notes which one is in play and links to it: - -- abstract domain types — [`#[cgp_type]`](../reference/macros/cgp_type.md) and the [abstract-types concept](../concepts/abstract-types.md) -- status-coded errors through an application-specific error component — [modular error handling](../concepts/modular-error-handling.md) over [`HasErrorType`](../reference/components/has_error_type.md) -- an async, per-endpoint-dispatched component — [`#[cgp_component]`](../reference/macros/cgp_component.md) with [`#[async_trait]`](../reference/macros/async_trait.md) -- handlers, and a business capability, that wrap another provider — [higher-order providers](../concepts/higher-order-providers.md) written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) and [`#[use_provider]`](../reference/attributes/use_provider.md) -- a backend reading context fields — [implicit field access](../concepts/implicit-arguments.md) via [`#[implicit]`](../reference/attributes/implicit.md) arguments, with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) reserved for the request fields a handler reads through a `where` bound -- organizing the wiring — path prefixes and a namespace, per the [namespaces-and-prefixes guide](../guides/namespaces-and-prefixes.md), backed by [`#[prefix]`](../reference/macros/cgp_namespace.md), [`#[default_impl]`](../reference/traits/default_namespace.md), and [`delegate_components!`](../reference/macros/delegate_components.md) with a [`check_components!`](../reference/macros/check_components.md) assertion -- restoring a `Send` bound for the HTTP server — the [recovering `Send` bounds concept](../concepts/send-bounds.md) - -All snippets assume `use cgp::prelude::*;`. The service speaks in terms of a handful of domain types kept abstract so the same handlers work whatever concrete types a deployment chooses. - -## Abstract domain types - -The service never names a concrete user id, currency, or amount; it names abstract types a context supplies. Each is a one-line [abstract-type component](../concepts/abstract-types.md) defined with [`#[cgp_type]`](../reference/macros/cgp_type.md), carrying only the bound the rest of the code needs — here, that every domain value can be displayed in an error message: - -```rust -#[cgp_type] -#[prefix(@app.auth.types in DefaultNamespace)] -pub trait HasUserIdType { - type UserId: Display; -} - -#[cgp_type] -#[prefix(@app.finance.types in DefaultNamespace)] -pub trait HasQuantityType { - type Quantity: Display; -} - -#[cgp_type] -#[prefix(@app.finance.types in DefaultNamespace)] -pub trait HasCurrencyType { - type Currency: Display; -} -``` - -Keeping these abstract is what lets one balance-query handler serve a context whose currency is a rich enum and another whose currency is a bare string, without rewriting the handler. The authentication types `HasPasswordType` and `HasHashedPasswordType` are defined the same way under `@app.auth.types`. The [`#[prefix(@path in DefaultNamespace)]`](../reference/macros/cgp_namespace.md) attribute on each files the component under a path — `@app.auth.types`, `@app.finance.types` — that the wiring section uses to organize the table; a first-time reader can ignore it until then, or read the [namespaces-and-prefixes guide](../guides/namespaces-and-prefixes.md) for why the types sit on a `types` sub-path apart from the logic. - -## Status-coded errors - -Endpoints fail with an HTTP status code, but the handlers never construct one directly — they raise through an application-specific error component so the mapping from a domain failure to a status code lives in one place. `CanRaiseHttpError` is a [component](../concepts/modular-error-handling.md) that turns a marker code and a detail value into the context's abstract [error type](../reference/components/has_error_type.md): - -```rust -#[cgp_component(HttpErrorRaiser)] -#[prefix(@app.error in DefaultNamespace)] -#[use_type(HasErrorType.Error)] -pub trait CanRaiseHttpError { - fn raise_http_error(_code: Code, detail: Detail) -> Error; -} - -pub struct ErrUnauthorized; -pub struct ErrBadRequest; -pub struct ErrNotFound; -``` - -The code markers double as a table from marker to status code through an ordinary trait, so a provider can turn `ErrUnauthorized` into `401` without a match: - -```rust -pub trait IsStatusCode { - fn status_code() -> StatusCode; -} - -impl IsStatusCode for ErrUnauthorized { - fn status_code() -> StatusCode { - StatusCode::UNAUTHORIZED - } -} -// ErrBadRequest -> BAD_REQUEST, ErrNotFound -> NOT_FOUND, … - -#[cgp_impl(new DisplayHttpError)] -#[use_type(HasErrorType.{Error = AppError})] -impl HttpErrorRaiser -where - Code: IsStatusCode, - Detail: Display, -{ - fn raise_http_error(_code: Code, detail: Detail) -> AppError { - AppError { - status_code: Code::status_code(), - detail: anyhow!("{detail}"), - } - } -} -``` - -`DisplayHttpError` is generic over both the code and the detail, so one provider serves every `raise_http_error` call whose detail is `Display`. It pins the abstract error to the concrete `AppError` with the [`#[use_type]` equality form](../guides/importing-abstract-types.md) — `HasErrorType.{Error = AppError}` — which is why the method can build an `AppError` directly. A sibling `HandleHttpErrorWithAnyhow` handles details that are already an `anyhow::Error`; the wiring picks between them per detail type. - -## The dispatched API-handler component - -Every endpoint is one case of a single component that dispatches on a marker type naming the API. The consumer trait `CanHandleApi` takes the endpoint marker as a generic parameter and, for that endpoint, fixes a `Request` and `Response` type and an async method that turns one into the other: - -```rust -#[cgp_component(ApiHandler)] -#[prefix(@app.api in DefaultNamespace)] -#[async_trait] -#[use_type(HasErrorType.Error)] -pub trait CanHandleApi { - type Request; - type Response; - - async fn handle_api( - &self, - _api: PhantomData, - request: Self::Request, - ) -> Result; -} - -pub struct TransferApi; -pub struct QueryBalanceApi; -``` - -[`#[cgp_component]`](../reference/macros/cgp_component.md) makes `ApiHandler` a wireable component so each endpoint can bind a different provider, and [`#[async_trait]`](../reference/macros/async_trait.md) keeps the async method's declaration lint-clean. Because the component is generic over `Api`, a context dispatches it per marker — `TransferApi` to one provider, `QueryBalanceApi` to another — through the [namespace path machinery](../guides/dispatching-per-type.md) shown in the wiring section, with no runtime branch: `PhantomData` carries the choice at the type level. - -## Endpoint handlers - -Each endpoint is a provider for `ApiHandler` that depends on business capabilities rather than on any concrete backend. The transfer endpoint reads the logged-in sender and the transfer details from its request, then calls the `CanTransferMoney` capability — itself an abstract async component the context implements however it likes: - -```rust -#[cgp_impl(new HandleTransfer)] -#[uses(CanTransferMoney, CanRaiseHttpError)] -#[use_type(HasErrorType.Error)] -impl ApiHandler -where - Request: HasLoggedInUser + HasTransferMoneyFields, -{ - type Request = Request; - type Response = (); - - async fn handle_api(&self, _api: PhantomData, request: Request) -> Result<(), Error> { - let sender = request.logged_in_user().as_ref().ok_or_else(|| { - Self::raise_http_error(ErrUnauthorized, "you must first login".into()) - })?; - - self.transfer_money(sender, request.recipient(), request.currency(), request.quantity()) - .await?; - - Ok(()) - } -} -``` - -The endpoint is generic over its request shape: `HandleTransfer` works for any `Request` that exposes a logged-in user and the transfer fields through the [getter traits](../reference/macros/cgp_auto_getter.md) named in its `where` clause, so the same logic serves whatever request struct a deployment decodes. The `Self: ...` capability bounds are [impl-side dependencies](../concepts/impl-side-dependencies.md) declared with [`#[uses]`](../guides/declaring-dependencies.md), holding the context to providing money-transfer and error-raising without those leaking into the consumer trait. - -The balance query has the same shape but returns a value, so it fixes a response type. Its `QueryBalanceResponse` stays generic over the context's abstract `Quantity`, and `#[derive(Serialize)]` lets the JSON wrapper encode it: - -```rust -#[derive(Serialize)] -pub struct QueryBalanceResponse -where - App: HasQuantityType, -{ - pub balance: App::Quantity, -} - -#[cgp_impl(new HandleQueryBalance)] -#[uses(CanQueryUserBalance, CanRaiseHttpError)] -#[use_type(HasErrorType.Error)] -impl ApiHandler -where - Request: HasLoggedInUser + HasQueryBalanceFields, -{ - type Request = Request; - type Response = QueryBalanceResponse; - - async fn handle_api( - &self, - _api: PhantomData, - request: Request, - ) -> Result, Error> { - let user = request.logged_in_user().as_ref().ok_or_else(|| { - Self::raise_http_error(ErrUnauthorized, "you must first login".into()) - })?; - - let balance = self.query_user_balance(user, request.currency()).await?; - - Ok(QueryBalanceResponse { balance }) - } -} -``` - -## Reusable handler wrappers - -Cross-cutting concerns are handlers that wrap another handler, which makes them [higher-order providers](../concepts/higher-order-providers.md): each takes an inner handler as a type parameter, declared with [`#[use_provider]`](../reference/attributes/use_provider.md), implements `ApiHandler` itself, and threads the call through — transforming the request or response on the way. Three small wrappers cover decoding, authentication, and JSON encoding. - -`HandleFromRequest` adapts the request type, letting an endpoint that wants a clean domain request sit behind a handler whose request is the raw type the HTTP layer produces: - -```rust -#[cgp_impl(new HandleFromRequest)] -#[use_type(HasErrorType.Error)] -#[use_provider(InHandler: ApiHandler)] -impl ApiHandler -where - Request: Into, -{ - type Request = Request; - type Response = InHandler::Response; - - async fn handle_api( - &self, - api: PhantomData, - request: Self::Request, - ) -> Result { - InHandler::handle_api(self, api, request.into()).await - } -} -``` - -`UseBasicAuth` authenticates before delegating, resolving a basic-auth header into a logged-in user and mutating the request in place; it depends on the `CanQueryUserHashedPassword` and `CanCheckPassword` capabilities: - -```rust -#[cgp_impl(new UseBasicAuth)] -#[uses(CanQueryUserHashedPassword, CanCheckPassword)] -#[use_type(HasErrorType.Error)] -#[use_provider(InHandler: ApiHandler)] -impl ApiHandler -where - InHandler::Request: HasLoggedInUserMut + HasBasicAuthHeader, - Self::UserId: Clone, -{ - type Request = InHandler::Request; - type Response = InHandler::Response; - - async fn handle_api( - &self, - api: PhantomData, - mut request: Self::Request, - ) -> Result { - if request.logged_in_user().is_none() - && let Some((user_id, password)) = request.basic_auth_header() - { - let m_hashed_password = self.query_user_hashed_password(user_id).await?; - - if let Some(hashed_password) = m_hashed_password - && Self::check_password(password, &hashed_password) - { - *request.logged_in_user() = Some(user_id.clone()); - } - } - - InHandler::handle_api(self, api, request).await - } -} -``` - -`ResponseToJson` adapts in the other direction, wrapping whatever the inner handler returns in an Axum `Json` envelope. Because each wrapper is itself an `ApiHandler`, they nest into a pipeline: `HandleFromRequest>>>` reads outside-in as the stages a request passes through — decode the raw request, JSON-encode the response, authenticate, run the endpoint — with each layer adding exactly one concern and the endpoint at the center oblivious to all of them. - -## The backend behind the capabilities - -The business capabilities are satisfied by a provider that reads its data from context fields. `UseMockedApp` is an in-memory backend that implements `UserBalanceQuerier`, `MoneyTransferrer`, and the auth capabilities by reaching into maps stored on the context, pulled in as [`#[implicit]`](../reference/attributes/implicit.md) arguments — the balances map is read by reference (`&Arc>`) with no clone and no getter trait to declare: - -```rust -#[cgp_impl(UseMockedApp)] -#[default_impl(@app.finance.UserBalanceQuerierComponent in MockNamespace)] -#[uses(CanRaiseHttpError)] -#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity, HasErrorType.Error)] -impl UserBalanceQuerier -where - UserId: Ord + Clone, - Currency: Ord + Clone, - Quantity: Clone, -{ - async fn query_user_balance( - &self, - user: &UserId, - currency: &Currency, - #[implicit] user_balances: &Arc>>, - ) -> Result { - let balances = user_balances.lock().await; - balances - .get(&(user.clone(), currency.clone())) - .cloned() - .ok_or_else(|| Self::raise_http_error(ErrNotFound, format!("user not found: {user}"))) - } -} -``` - -The `#[implicit]` argument reads the same `user_balances` field a getter would, but as a `&Arc>` bound at the top of the method — the [preferred form](../guides/reading-context-fields.md) for a field a provider reads from its own context. The request-field getters `HasLoggedInUser` and `HasBasicAuthHeader`, by contrast, stay [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) traits, because they read from the *request* type and are required as `where` bounds on it (`Request: HasBasicAuthHeader`) — a case an implicit argument, which reads only from `self`, cannot cover. The [`#[default_impl]`](../reference/traits/default_namespace.md) attribute registers this provider into the application's namespace; that is a wiring concern, explained next. - -A business capability can be wrapped the same way an API handler can. `NoTransferToSelf` is a [higher-order provider](../concepts/higher-order-providers.md) for `MoneyTransferrer` that rejects a self-transfer and otherwise delegates to an inner transfer provider: - -```rust -#[cgp_impl(new NoTransferToSelf)] -#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity, HasErrorType.Error)] -#[uses(CanRaiseHttpError)] -#[use_provider(InHandler: MoneyTransferrer)] -impl MoneyTransferrer -where - UserId: Eq, -{ - async fn transfer_money( - &self, - sender: &UserId, - recipient: &UserId, - currency: &Currency, - quantity: &Quantity, - ) -> Result<(), Error> { - if sender != recipient { - InHandler::transfer_money(self, sender, recipient, currency, quantity).await - } else { - Err(Self::raise_http_error( - ErrBadRequest, - format!("cannot transfer with the same sender and recipient: {sender}"), - )) - } - } -} -``` - -A real deployment would swap `UseMockedApp` for a database-backed provider. Since the backend is selected per context in the wiring, replacing it with a `UsePostgres` provider that implements the same capabilities changes which backend runs without touching a single endpoint or wrapper. - -## Organizing the wiring with a namespace - -A concrete context becomes the running application by resolving every abstract type and component. Rather than spell all of that out on the context, the application lifts it into a reusable **namespace** — the [namespaces-and-prefixes guide](../guides/namespaces-and-prefixes.md) develops this technique in full; the essentials are shown here. `MockNamespace` inherits the built-in `DefaultNamespace` and wires the pieces that have no `#[cgp_impl]` block of their own — the concrete error type, the per-detail HTTP-error dispatch, and the abstract type choices — as [namespace body entries](../reference/macros/cgp_namespace.md) keyed by the paths the `#[prefix]` attributes established: - -```rust -cgp_namespace! { - new MockNamespace: DefaultNamespace { - @cgp.core.error.ErrorTypeProviderComponent: - UseType, - - @app.error.HttpErrorRaiserComponent. Code.String: - DisplayHttpError, - @app.error.HttpErrorRaiserComponent. Code.anyhow::Error: - HandleHttpErrorWithAnyhow, - - @app.auth.types.{ - UserIdTypeProviderComponent, - PasswordTypeProviderComponent, - HashedPasswordTypeProviderComponent, - }: - UseType, - @app.finance.types.QuantityTypeProviderComponent: - UseType, - @app.finance.types.CurrencyTypeProviderComponent: - UseType, - } -} -``` - -The business-logic providers, which *do* have `#[cgp_impl]` blocks, register themselves into the same namespace from their own definition with the [`#[default_impl(@path in MockNamespace)]`](../reference/traits/default_namespace.md) attribute seen on `UseMockedApp`'s `UserBalanceQuerier` above — so `MockNamespace` resolves `@app.finance.UserBalanceQuerierComponent` to `UseMockedApp` without a line in its body. Registering the wiring next to the implementation it wires is what keeps the namespace body down to the handful of entries that have nowhere else to live. - -The application's API surface is a separate reusable table keyed by the API marker, so any backend can pull it in: - -```rust -cgp_namespace! { - new DefaultApiHandlers { - QueryBalanceApi: - HandleFromRequest< - AxumQueryBalanceRequest, - ResponseToJson>>, - >, - TransferApi: - HandleFromRequest< - AxumTransferRequest, - UseBasicAuth>, - >, - } -} -``` - -With the backend in a namespace and the API surface in a table, the context's own wiring shrinks to three statements: join the namespace, pull the API handlers onto the `ApiHandler` dispatch path with a `for` loop, and override the one component it wants to treat specially — wrapping money transfers in `NoTransferToSelf`: - -```rust -#[derive(HasField, Default)] -pub struct MockApp { - pub user_balances: Arc>>, - pub user_passwords: BTreeMap, -} - -delegate_components! { - MockApp { - namespace MockNamespace; - - for in DefaultApiHandlers { - @app.api.ApiHandlerComponent.Key: Value, - } - - @app.finance.MoneyTransferrerComponent: - NoTransferToSelf, - } -} -``` - -Each remaining line states a decision rather than a mechanical fact: `MockApp` uses the mock backend, serves the default API surface, and guards transfers. The override works because `MockNamespace` deliberately does *not* register `@app.finance.MoneyTransferrerComponent` — the base `MoneyTransferrer` provider is used only as the inner handler of `NoTransferToSelf` — so the context is free to wire that path directly; had the namespace claimed it, the two entries would conflict. The two endpoints assemble different pipelines from the same parts: both decode and authenticate, but only the balance query wraps its response in `ResponseToJson`, since the transfer returns nothing. - -Because CGP wiring is [checked lazily](../concepts/check-traits.md), a companion [`check_components!`](../reference/macros/check_components.md) block proves at compile time that every endpoint is fully satisfied, listing the API markers to verify for the generic `ApiHandler` component: - -```rust -check_components! { - MockApp { - QuantityTypeProviderComponent, - UserBalanceQuerierComponent, - MoneyTransferrerComponent, - ApiHandlerComponent: [ - QueryBalanceApi, - TransferApi, - ], - } -} -``` - -## Serving over HTTP - -Handing the handlers to an HTTP server needs one bound the component cannot provide: that each handler's future is `Send`. Axum runs on a multi-threaded, work-stealing runtime that may move a task between threads while it is suspended, so the futures it drives must be `Send` — but the `async fn` in `CanHandleApi` desugars to a bare `impl Future` with no such bound, and stable Rust has no way to require it generically. The fix is a plain trait whose method declares `+ Send` directly and which is implemented for the concrete context, where the compiler can verify the bound itself: - -```rust -pub trait CanHandleApiSend: - CanHandleApi + Send + Sync -{ - fn handle_api_send( - &self, - _api: PhantomData, - request: Self::Request, - ) -> impl Future> + Send; -} - -impl CanHandleApiSend for MockApp { - async fn handle_api_send( - &self, - api: PhantomData, - request: Self::Request, - ) -> Result { - self.handle_api(api, request).await - } -} - -// … and the same one-line forwarding impl for TransferApi. -``` - -Each impl just forwards to `handle_api`, but at a concrete context and API the awaited future is a concrete type whose `Send`-ness the compiler can confirm — which is why the impls cannot be folded into one generic blanket impl. The full reasoning, and why this is a stand-in for the Return Type Notation stable Rust lacks, is in [recovering `Send` bounds](../concepts/send-bounds.md). - -With `CanHandleApiSend` in hand, the routing layer bounds `App: CanHandleApiSend` and mounts each endpoint. An `add_route` on an Axum `Router` reads the request out of the HTTP layer, calls `handle_api_send`, and maps a raised `AppError` to its status code, so a single `add_main_api_routes` assembles the whole service: - -```rust -impl CanAddMainApiRoutes for Router> -where - Self: CanAddRoute + CanAddRoute, -{ - fn add_main_api_routes(self) -> Self { - self.add_route(PhantomData::<(QueryBalanceApi, GetMethod)>, "/balance") - .add_route(PhantomData::<(TransferApi, PostMethod)>, "/transfer") - } -} -``` - -The `main` function then constructs a `MockApp`, builds the router with `add_main_api_routes`, and serves it — completing the path from a request on the wire, through the decode-authenticate-handle-encode pipeline the namespace wired, to a JSON response or a status-coded error. diff --git a/docs/examples/profile-picture.md b/docs/examples/profile-picture.md deleted file mode 100644 index 93484ba1..00000000 --- a/docs/examples/profile-picture.md +++ /dev/null @@ -1,267 +0,0 @@ -# Profile picture lookup - -This example fetches a user's profile picture — a real-world operation that queries a database for a user record and, if one is set, downloads and decodes the image from object storage. It progresses from two field-driven async functions to a fully wired application that swaps its database engine and its storage backend per context without touching the orchestration logic. It is a template for any use case where one business operation composes several infrastructure steps, each of which may have more than one implementation. - -The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: - -- context-generic functions — [`#[cgp_fn]`](../reference/macros/cgp_fn.md) with [implicit arguments](../concepts/implicit-arguments.md) -- async methods in traits — [`#[async_trait]`](../reference/macros/async_trait.md) -- composing capabilities — [`#[uses]`](../reference/attributes/uses.md) -- field access on contexts — [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) -- impl-only generic parameters — [`#[impl_generics]`](../reference/macros/cgp_fn.md) -- components and named providers — [`#[cgp_component]`](../reference/macros/cgp_component.md), [`#[cgp_impl]`](../reference/macros/cgp_impl.md), and the [consumer/provider trait duality](../concepts/consumer-and-provider-traits.md) -- wiring a context to providers — [`delegate_components!`](../reference/macros/delegate_components.md) - -All snippets assume `use cgp::prelude::*;`. The operation works over two domain types — a `UserId` newtype and a `User` row that may carry the storage key of a profile picture: - -```rust -pub struct UserId(pub u64); - -#[derive(sqlx::FromRow)] -pub struct User { - pub name: String, - pub email: String, - pub profile_picture_object_id: Option, -} -``` - -## Field-driven steps - -The two infrastructure steps are each a function that reads its connection from the context's fields rather than from explicit arguments. Marking a function with [`#[cgp_fn]`](../reference/macros/cgp_fn.md) and tagging a parameter [`#[implicit]`](../reference/attributes/implicit.md) turns it into a method on any context that carries a matching field; the remaining parameters stay as ordinary arguments the caller supplies. Both functions are async, so each also carries [`#[async_trait]`](../reference/macros/async_trait.md), which keeps the generated trait's async method lint-clean: - -```rust -#[cgp_fn] -#[async_trait] -pub async fn get_user( - &self, - #[implicit] database: &PgPool, - user_id: &UserId, -) -> anyhow::Result { - let user = - sqlx::query_as("SELECT name, email, profile_picture_object_id FROM users WHERE id = $1") - .bind(user_id.0 as i64) - .fetch_one(database) - .await?; - - Ok(user) -} - -#[cgp_fn] -#[async_trait] -pub async fn fetch_storage_object( - &self, - #[implicit] storage_client: &Client, - #[implicit] bucket_id: &str, - object_id: &str, -) -> anyhow::Result> { - let output = storage_client - .get_object() - .bucket(bucket_id) - .key(object_id) - .send() - .await?; - - let data = output.body.collect().await?.into_bytes().to_vec(); - Ok(data) -} -``` - -`get_user` reads one implicit field, `database`; `fetch_storage_object` reads two, `storage_client` and `bucket_id`, in addition to its explicit `object_id`. A context becomes eligible for each function purely by deriving [`HasField`](../reference/derives/derive_has_field.md) for fields whose names and types match the implicit arguments — no per-context implementation is written: - -```rust -#[derive(HasField)] -pub struct App { - pub database: PgPool, - pub storage_client: Client, - pub bucket_id: String, -} - -#[derive(HasField)] -pub struct MinimalApp { - pub database: PgPool, -} -``` - -`App` has all three fields, so it can call both functions; `MinimalApp` has only `database`, so it can call `get_user` but the compiler refuses any call to `fetch_storage_object` on it. - -## Composing the steps - -The orchestration is itself a `#[cgp_fn]` that calls the two steps as methods on `self`. Because those calls are CGP capabilities rather than inherent methods, the function declares them with [`#[uses]`](../reference/attributes/uses.md), which adds each as a hidden bound on the context instead of a visible parameter: - -```rust -#[cgp_fn] -#[async_trait] -#[uses(GetUser, FetchStorageObject)] -pub async fn get_user_profile_picture( - &self, - user_id: &UserId, -) -> anyhow::Result> { - let user = self.get_user(user_id).await?; - - if let Some(object_id) = user.profile_picture_object_id { - let data = self.fetch_storage_object(&object_id).await?; - let image = image::load_from_memory(&data)?.to_rgb8(); - - Ok(Some(image)) - } else { - Ok(None) - } -} -``` - -The names in `#[uses(GetUser, FetchStorageObject)]` are the traits `#[cgp_fn]` derives from the two step functions — a function `foo_bar` generates a trait `FooBar`. The body reads as plain method calls; `#[uses]` threads the trait bounds behind the scenes so that only a context carrying every required field gains `get_user_profile_picture`. `App` does; `MinimalApp` does not, and the gap is a compile error rather than a runtime failure. - -## Varying the database engine - -`get_user` above hardcodes `&PgPool`, which ties it to PostgreSQL. To let one implementation serve several database engines, introduce a type parameter that lives on the impl alone — never on the generated trait or its callers — with [`#[impl_generics]`](../reference/macros/cgp_fn.md). The implicit `database` field becomes `&Pool`, and the `where` clause carries the engine-specific bounds: - -```rust -#[cgp_fn] -#[async_trait] -#[impl_generics(Db: Database)] -pub async fn get_user( - &self, - #[implicit] database: &Pool, - user_id: &UserId, -) -> anyhow::Result -where - i64: sqlx::Type, - for<'a> User: sqlx::FromRow<'a, Db::Row>, - for<'a> i64: sqlx::Encode<'a, Db>, - for<'a> ::Arguments<'a>: sqlx::IntoArguments<'a, Db>, - for<'a> &'a mut ::Connection: sqlx::Executor<'a, Database = Db>, -{ - let user = - sqlx::query_as("SELECT name, email, profile_picture_object_id FROM users WHERE id = $1") - .bind(user_id.0 as i64) - .fetch_one(database) - .await?; - - Ok(user) -} -``` - -Each context supplies `Db` implicitly through the type of its `database` field. An `App` carrying a `PgPool` resolves `Db = Postgres`, while an embedded context carrying a `SqlitePool` resolves `Db = Sqlite` — `SqlitePool` is `Pool` under the hood — and both satisfy the sqlx bounds independently: - -```rust -#[derive(HasField)] -pub struct EmbeddedApp { - pub database: SqlitePool, - pub storage_client: Client, - pub bucket_id: String, -} -``` - -`get_user_profile_picture` is unchanged: it depends on the `GetUser` capability, not on which engine satisfies it, so the same orchestration now runs on PostgreSQL and SQLite alike. - -## Varying the storage backend - -A single `#[cgp_fn]` defines exactly one implementation, so it cannot offer the storage fetch in more than one flavor. When a step needs interchangeable implementations — say Amazon S3 in one deployment and Google Cloud Storage in another — promote it to a [component](../concepts/consumer-and-provider-traits.md) with [`#[cgp_component]`](../reference/macros/cgp_component.md). The annotated `CanFetchStorageObject` trait is the *consumer trait* callers use; the `StorageObjectFetcher` argument names the generated *provider trait* that implementations target: - -```rust -#[async_trait] -#[cgp_component(StorageObjectFetcher)] -pub trait CanFetchStorageObject { - async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result>; -} -``` - -Each backend is a *named provider* written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md). Unlike a blanket `impl`, named providers may overlap freely, and `#[implicit]` works inside them exactly as in `#[cgp_fn]`, so each provider reads whatever connection field its backend needs: - -```rust -#[cgp_impl(new FetchS3Object)] -impl StorageObjectFetcher { - async fn fetch_storage_object( - &self, - #[implicit] storage_client: &Client, - #[implicit] bucket_id: &str, - object_id: &str, - ) -> anyhow::Result> { - let output = storage_client - .get_object() - .bucket(bucket_id) - .key(object_id) - .send() - .await?; - - let data = output.body.collect().await?.into_bytes().to_vec(); - Ok(data) - } -} - -#[cgp_impl(new FetchGCloudObject)] -impl StorageObjectFetcher { - async fn fetch_storage_object( - &self, - #[implicit] storage_client: &Storage, - #[implicit] bucket_id: &str, - object_id: &str, - ) -> anyhow::Result> { - let mut reader = storage_client - .read_object(bucket_id, object_id) - .send() - .await?; - - let mut contents = Vec::new(); - while let Some(chunk) = reader.next().await.transpose()? { - contents.extend_from_slice(&chunk); - } - - Ok(contents) - } -} -``` - -`FetchS3Object` reads an `aws_sdk_s3::Client` from its `storage_client` field; `FetchGCloudObject` reads a `google_cloud_storage::client::Storage` from the same field name. The `new` keyword in each attribute defines the provider struct in place. - -The orchestration now imports the storage step by its *consumer* trait name, so it depends on the capability rather than on any one backend: - -```rust -#[cgp_fn] -#[async_trait] -#[uses(GetUser, CanFetchStorageObject)] -pub async fn get_user_profile_picture( - &self, - user_id: &UserId, -) -> anyhow::Result> { - let user = self.get_user(user_id).await?; - - if let Some(object_id) = user.profile_picture_object_id { - let data = self.fetch_storage_object(&object_id).await?; - let image = image::load_from_memory(&data)?.to_rgb8(); - - Ok(Some(image)) - } else { - Ok(None) - } -} -``` - -## Wiring contexts to backends - -Defining a provider does not attach it to any context; a context chooses its provider by wiring with [`delegate_components!`](../reference/macros/delegate_components.md). Each entry maps the component — keyed by its generated `…Component` name — to the provider that implements it for that context. An `App` carrying an S3 client wires to `FetchS3Object`, while a `GCloudApp` carrying a GCloud client wires to `FetchGCloudObject`: - -```rust -#[derive(HasField)] -pub struct GCloudApp { - pub database: PgPool, - pub storage_client: Storage, - pub bucket_id: String, -} - -delegate_components! { - App { - StorageObjectFetcherComponent: FetchS3Object, - } -} - -delegate_components! { - GCloudApp { - StorageObjectFetcherComponent: FetchGCloudObject, - } -} -``` - -Both contexts remain plain data structs with `#[derive(HasField)]`; all backend selection happens in the wiring block, resolved at compile time with no runtime dispatch. The S3 binary contains only the S3 code path and the GCloud binary only the GCloud one. Adding a third backend — Azure Blob Storage, say — is a new `#[cgp_impl(new FetchAzureObject)]` provider and one more `delegate_components!` entry; `get_user`, `get_user_profile_picture`, and every existing context stay untouched. - - diff --git a/docs/examples/shell-scripting-dsl.md b/docs/examples/shell-scripting-dsl.md deleted file mode 100644 index e13cfb29..00000000 --- a/docs/examples/shell-scripting-dsl.md +++ /dev/null @@ -1,261 +0,0 @@ -# Shell-scripting DSL - -This example builds a type-level shell-scripting DSL whose programs are ordinary Rust *types*, interpreted at compile time by whichever context runs them. It progresses from a fixed CLI program, through the component that interprets a program and the namespace that wires the interpreters, to a custom context that supplies runtime values and a language extension that adds new syntax — and is a template for any embedded DSL where the program's *syntax* should be decoupled from its *semantics* so that each can vary independently. The general pattern it instantiates is described in [type-level DSLs](../concepts/type-level-dsls.md). - -The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: - -- the computation component the DSL is built on — [`Handler` / `CanHandle`](../reference/components/handler.md) in the [handler family](../concepts/handlers.md) -- writing a provider that interprets one syntax — [`#[cgp_impl]`](../reference/macros/cgp_impl.md) with a pattern-matched `Code` parameter -- raising source errors into the context's abstract error — [`CanRaiseError`](../reference/components/can_raise_error.md) -- dispatching on the program type — [`UseDelegate`](../reference/providers/use_delegate.md) and [dispatching](../concepts/dispatching.md) -- bundling and inheriting wiring — [namespaces](../concepts/namespaces.md) with [`cgp_namespace!`](../reference/macros/cgp_namespace.md) -- joining a namespace from a context — [`delegate_components!`](../reference/macros/delegate_components.md) and [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) -- composing handlers into a pipeline provider — [`PipeHandlers`](../reference/providers/handler_combinators.md) - -All snippets assume `use cgp::prelude::*;`, and the handler items come from `cgp::extra::handler`. - -## A program is a type - -The smallest DSL program is a type, not a value. The `hypershell!` macro provides shell-like surface syntax — a pipe operator and bracketed argument lists — that desugars into a chain of phantom-typed syntax markers. This program runs `echo hello world!` and streams the result to standard output: - -```rust -pub type Program = hypershell! { - SimpleExec< - StaticArg<"echo">, - WithStaticArgs["hello", "world!"], - > - | StreamToStdout -}; -``` - -The macro is sugar only: the `|` desugars into a `Pipe` of handler syntaxes, the bracketed `[...]` shorthand into a `` type-level list, and each string literal into a `Symbol!` type-level string, since a program lives entirely at the type level where a `&str` value cannot appear. The program above expands to exactly this plain type: - -```rust -pub type Program = Pipe, - WithStaticArgs, - >, - StreamToStdout, -]>; -``` - -The macro is entirely optional — writing this type by hand is equivalent. The program carries no data; it is a description of *what* to do, with the *how* supplied separately by the context that runs it. - -A context runs a program by calling `handle`, passing the program as a `PhantomData` type argument and an input value. `HypershellCli` is a predefined empty context for CLI-only programs, so it can be constructed directly: - -```rust -#[tokio::main] -async fn main() -> Result<(), Error> { - HypershellCli - .handle(PhantomData::, Vec::new()) - .await?; - - Ok(()) -} -``` - -The `Vec::new()` is the program's standard input, which `echo` ignores. Running it prints `hello world!`. - -## The component that interprets a program - -Every step of a program is interpreted by one component: the [`Handler`](../reference/components/handler.md) component, the async, fallible corner of the [handler family](../concepts/handlers.md). Its consumer trait `CanHandle` is what `handle` above resolves to: - -```rust -#[async_trait] -#[cgp_component(Handler)] -#[derive_delegate(UseDelegate)] -#[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanHandle { - type Output; - - async fn handle( - &self, - _tag: PhantomData, - input: Input, - ) -> Result; -} -``` - -The `Code` parameter is the program fragment being interpreted, `Input` is the data flowing in (a process's standard input, an HTTP body), and the associated `Output` is what the fragment produces. The phantom `PhantomData` is how a *type* is passed where a value is expected: one context hosts many handlers, each keyed by a distinct `Code` tag, and the wiring dispatches on that tag. Because the program is just a `Code` type, interpreting it is a matter of resolving `CanHandle` for that type. - -## Abstract syntax, decoupled from its meaning - -A syntax marker like `SimpleExec` is nothing but a phantom struct — it has no interpreter attached: - -```rust -pub struct SimpleExec(pub PhantomData<(Path, Args)>); -``` - -This is the whole point of the design: how a program is *written* is completely decoupled from how it is *interpreted*. `SimpleExec` names a piece of syntax; what it *does* is decided entirely by which provider a context wires for the `Code = SimpleExec<…>` case. Swapping that provider — to run commands on a different async runtime, say — changes the program's meaning without touching a single program that uses `SimpleExec`. The syntax is the DSL's abstract grammar; the providers are its interpreters. - -## Interpreting one syntax with a provider - -An interpreter is a `Handler` provider that pattern-matches on a single syntax type through its `Code` parameter. Written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), it reads like an ordinary method implementation — `self` is the context — while the context stays generic. This provider interprets a `Checksum` syntax by consuming a stream of bytes and producing their digest: - -```rust -pub struct Checksum(pub PhantomData); - -#[cgp_impl(new HandleStreamChecksum)] -#[uses(CanRaiseError)] -#[use_type(HasErrorType.Error)] -impl Handler, Input> -where - Input: Unpin + TryStream, - Hasher: Digest, - Input::Ok: AsRef<[u8]>, -{ - type Output = GenericArray; - - async fn handle( - &self, - _tag: PhantomData>, - mut input: Input, - ) -> Result { - let mut hasher = Hasher::new(); - - while let Some(bytes) = input.try_next().await.map_err(Self::raise_error)? { - hasher.update(bytes); - } - - Ok(hasher.finalize()) - } -} -``` - -Two things make this provider reusable across contexts. It matches `Handler, …>` for *any* `Hasher` that implements `Digest`, so the one provider covers every hash algorithm. And it never names a concrete error type: the `CanRaiseError` bound lets it convert a stream error into the context's own abstract error via [`CanRaiseError`](../reference/components/can_raise_error.md), so a context using `anyhow`, `eyre`, or a bespoke error type all reuse the same code. The [`#[uses(...)]`](../reference/attributes/uses.md) import and the `where` clause together state everything the provider needs from the context as [impl-side dependencies](../concepts/impl-side-dependencies.md); a context that cannot meet them simply cannot wire this provider. - -## Dispatching on the program - -A single provider only interprets one syntax, so something must route each `Code` to its interpreter. That is the job of [`UseDelegate`](../reference/providers/use_delegate.md): because the `Handler` component is declared `#[derive_delegate(UseDelegate)]`, the `HandlerComponent` lookup can be keyed on the `Code` type through an inner type-level table, as described in [dispatching](../concepts/dispatching.md). A wiring entry therefore reaches *past* the component name to the `Code` it handles — written as a dotted path under `HandlerComponent`, with the syntax's generic parameters bound by a leading `<…>`: - -```rust -@HandlerComponent. SimpleExec: - HandleSimpleExec, -@HandlerComponent. StreamingExec: - HandleStreamingExec, -``` - -The keys are *types*, not values, which is what lets a lookup capture generic parameters like `Path` and `Args` and still match every use of `SimpleExec`. Resolving `CanHandle, …>` for a context then walks one extra step through the `Code` table: `HandlerComponent` dispatches on `SimpleExec<…>` to `HandleSimpleExec`. - -## Bundling the wiring into a namespace - -A real DSL has many syntaxes wired to many providers, and several contexts that should share that wiring. A [namespace](../concepts/namespaces.md) captures the whole table once and lets contexts inherit it. `HypershellNamespace` is defined with [`cgp_namespace!`](../reference/macros/cgp_namespace.md), inheriting CGP's built-in `DefaultNamespace` and adding entries that map groups of syntaxes to their interpreters under the `HandlerComponent` path: - -```rust -cgp_namespace! { - new HypershellNamespace: DefaultNamespace { - @cgp.core.error.ErrorTypeProviderComponent: - UseAnyhowError, - - @cgp.extra.handler.HandlerComponent.[ - SimpleExec, - StreamingExec, - StreamToStdout, - ]: - HypershellTokioProvider, - - @cgp.extra.handler.HandlerComponent.[ - SimpleHttpRequest, - StreamingHttpRequest, - ]: - HypershellReqwestProvider, - } -} -``` - -Each entry is keyed by a *path* — a dotted sequence written with the `@` sigil, like `@cgp.extra.handler.HandlerComponent` — rather than a bare component name, which is what lets a namespace inherit another's entries and lets a context override a single one without disturbing the rest. The array form groups several `Code` keys that share one provider. Because the wiring is expressed through trait resolution, all of this dispatch is resolved at compile time with no runtime indirection. - -## Running on a custom context - -A program that reads runtime values — a URL, a name — needs a context that carries them. Deriving [`HasField`](../reference/derives/derive_has_field.md) exposes a struct's fields so that syntaxes like `FieldArg<"name">` can read them, and joining `HypershellNamespace` inside [`delegate_components!`](../reference/macros/delegate_components.md) inherits every interpreter at once: - -```rust -pub type Program = hypershell! { - SimpleExec< - StaticArg<"echo">, - WithArgs[ - StaticArg<"Hello,">, - FieldArg<"name">, - ], - > - | StreamToStdout -}; - -#[derive(HasField)] -pub struct MyApp { - pub name: String, -} - -delegate_components! { - MyApp { - namespace HypershellNamespace; - } -} -``` - -The `namespace HypershellNamespace;` header makes every lookup `MyApp` cannot resolve directly fall back to the namespace's entries, so `MyApp` interprets the full DSL without restating any wiring. `WithArgs` mixes static arguments with `FieldArg<"name">`, which resolves through `HasField` to the context's `name` field. Constructing `MyApp { name: "Alice".into() }` and calling `handle` prints `Hello, Alice`. The predefined `HypershellCli` is wired the same way with an empty struct — it joins `HypershellNamespace` and adds nothing. - -## Extending the DSL - -A language extension adds new syntax and its interpreters without forking the core. Define the new syntax markers, write providers for them, and publish a namespace that inherits `HypershellNamespace` and layers the new entries on top. Here a `Checksum` syntax (interpreted by `HandleStreamChecksum` from earlier) and a `BytesToHex` syntax join the language: - -```rust -pub struct BytesToHex; - -#[cgp_impl(new HandleBytesToHex)] -#[use_type(HasErrorType.Error)] -impl Handler -where - Input: AsRef<[u8]>, -{ - type Output = String; - - async fn handle( - &self, - _tag: PhantomData, - input: Input, - ) -> Result { - Ok(hex::encode(input)) - } -} - -cgp_namespace! { - new HypershellChecksumNamespace: HypershellNamespace { - @cgp.extra.handler.HandlerComponent. Checksum: - PipeHandlers, - - @cgp.extra.handler.HandlerComponent.BytesToHex: - HandleBytesToHex, - } -} -``` - -`HandleBytesToHex` interprets *any* `Code` whose `Input` is byte-like — it does not inspect the tag — showing that a provider can be as generic as its logic allows. The `Checksum` entry wires not a single provider but a [`PipeHandlers`](../reference/providers/handler_combinators.md) composition: `HandleToFuturesStream` adapts the incoming stream into the `TryStream` that `HandleStreamChecksum` expects, and the two run as one handler. `HypershellChecksumNamespace` inherits everything `HypershellNamespace` resolves and adds the two new keys, so a context joining it speaks the extended language: - -```rust -pub type Program = hypershell! { - StreamingHttpRequest, WithHeaders[]> - | Checksum - | BytesToHex - | StreamToStdout -}; - -#[derive(HasField)] -pub struct MyApp { - pub http_client: Client, - pub url: String, -} - -delegate_components! { - MyApp { - namespace HypershellChecksumNamespace; - } -} -``` - -The program fetches a URL, hashes the streamed response natively, hex-encodes the digest, and prints it — and the only change from joining the core namespace is the namespace name. Because a namespace is just one more entry in an inheritance chain, an extension composes onto the language the same way a context composes onto a namespace, with the whole resolution settled at compile time. diff --git a/docs/examples/social-media-app.md b/docs/examples/social-media-app.md deleted file mode 100644 index e69777a2..00000000 --- a/docs/examples/social-media-app.md +++ /dev/null @@ -1,388 +0,0 @@ -# Social media app - -This example builds the CRUD backend for a small social media service — managing users and posts — and follows it as the wiring grows from a handful of components into something a real application would have. It progresses from one coarse manager trait per domain, through fine-grained per-operation traits and a higher-order provider that adds input filtering, to provider bundles and finally namespace-grouped wiring that keeps the top-level configuration short even as the component count climbs. It is a template for any application whose component count grows past the point where a flat delegation table stays readable. - -The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: - -- consumer/provider trait pairs — [`#[cgp_component]`](../reference/macros/cgp_component.md) and [consumer and provider traits](../concepts/consumer-and-provider-traits.md) -- providers that read context fields as method arguments — [`#[cgp_impl]`](../reference/macros/cgp_impl.md) with [`#[implicit]`](../reference/attributes/implicit.md) arguments backed by [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) -- importing a capability a provider depends on — [`#[uses]`](../reference/attributes/uses.md), an [impl-side dependency](../concepts/impl-side-dependencies.md) -- a provider that wraps another provider — [higher-order providers](../concepts/higher-order-providers.md) and [`#[use_provider]`](../reference/attributes/use_provider.md) -- wiring a context and bundling providers into reusable groups — [`delegate_components!`](../reference/macros/delegate_components.md) -- grouping component keys so a context inherits a whole bundle at once — [namespaces](../concepts/namespaces.md), the [`#[prefix(...)]`](../reference/macros/cgp_component.md) attribute, and [`cgp_namespace!`](../reference/macros/cgp_namespace.md) -- checking that a wiring is complete — [`check_components!`](../reference/macros/check_components.md) - -All snippets assume `use cgp::prelude::*;` and share a small set of domain types — the entities the service manipulates and the database handle the providers read: - -```rust -pub struct Email(pub String); - -pub struct User; -pub struct UserData; -pub struct UserId; - -pub struct Post; -pub struct PostId; - -pub enum Error { - InvalidUsername, - InvalidMessage, -} - -pub struct PostgresDb; - -#[derive(PartialOrd, PartialEq)] -pub struct Probability(pub f64); - -impl Probability { - pub const fn new(value: f64) -> Self { - Self(value) - } -} -``` - -## One manager per domain - -The first cut gives each domain a single manager trait that exposes all of its operations. A `UserManager` covers the user lifecycle and a `PostManager` the post lifecycle: - -```rust -#[cgp_component(UserManager)] -pub trait CanManageUser { - fn create_user(&self, username: &str, email: &Email) -> Result; - - fn get_user(&self, user_id: &UserId) -> Result; - - fn update_user_data(&self, user_id: &UserId, user_data: &UserData) -> Result<(), Error>; -} - -#[cgp_component(PostManager)] -pub trait CanManagePost { - fn create_post(&self, title: &str, content: &str) -> Result; - - fn get_post(&self, post_id: &PostId) -> Result; - - fn update_post(&self, post_id: &PostId, content: &str) -> Result<(), Error>; - - fn delete_post(&self, post_id: &PostId) -> Result<(), Error>; -} -``` - -Each trait is an ordinary trait turned into a CGP component by [`#[cgp_component]`](../reference/macros/cgp_component.md), which names the provider trait (`UserManager`, `PostManager`) that implementers write against. The service also needs two content-safety checks — rejecting banned usernames and spam posts — so those become components of their own, each returning a `Probability` the managers can threshold: - -```rust -#[cgp_component(UsernameCensor)] -pub trait CanCensorUsername { - fn username_is_censored(&self, username: &str) -> Probability; -} - -#[cgp_component(SpamMessageDetector)] -pub trait CanDetectSpamMessage { - fn message_is_spam(&self, message: &str) -> Probability; -} -``` - -The production manager talks to PostgreSQL. Its provider reads the database handle straight out of the context as an [`#[implicit]`](../reference/attributes/implicit.md) argument — CGP extracts the `database` field from the context rather than threading it through every call site — and pulls in `CanCensorUsername` with [`#[uses]`](../reference/attributes/uses.md) so `create_user` can reject censored names before writing: - -```rust -#[cgp_impl(new PostgresUserManager)] -#[uses(CanCensorUsername)] -impl UserManager { - fn create_user( - &self, - #[implicit] database: &PostgresDb, - username: &str, - email: &Email, - ) -> Result { - if self.username_is_censored(username) > Probability::new(0.8) { - return Err(Error::InvalidUsername); - } - - // ... insert the user with `database` - } - - fn get_user(&self, #[implicit] database: &PostgresDb, user_id: &UserId) -> Result { - // ... - } - - fn update_user_data( - &self, - #[implicit] database: &PostgresDb, - user_id: &UserId, - user_data: &UserData, - ) -> Result<(), Error> { - // ... - } -} -``` - -`PostgresUserManager` is a provider written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), so its body reads like methods on the context while staying generic over any context that supplies a `database` field and a `CanCensorUsername` implementation. The `PostManager` provider follows the same shape, thresholding `CanDetectSpamMessage` inside `create_post`. The content checks themselves get cheap stand-in providers for now — `DummyUserCensor` and `DummySpamMessageDetector` — that always pass. - -A concrete context ties it together. `ProductionApp` holds the database handle and names a provider for every component in one [`delegate_components!`](../reference/macros/delegate_components.md) table: - -```rust -#[derive(HasField)] -pub struct ProductionApp { - pub database: PostgresDb, -} - -delegate_components! { - ProductionApp { - UserManagerComponent: PostgresUserManager, - PostManagerComponent: PostgresPostManager, - UsernameCensorComponent: DummyUserCensor, - SpamMessageDetectorComponent: DummySpamMessageDetector, - } -} -``` - -The [`#[derive(HasField)]`](../reference/derives/derive_has_field.md) is what makes the `#[implicit] database` arguments resolve, by exposing the `database` field for [`HasField`](../reference/traits/has_field.md) lookup. This wiring is compact, but it hides a strain that grows with the application: `PostgresUserManager` depends on `CanCensorUsername` even though only `create_user` uses it, so `get_user` and `update_user_data` carry a dependency they never touch. As managers accumulate methods, these spurious dependencies pile up, and there is no way to grant `create_user` more capabilities without granting them to the whole manager. - -## One trait per operation - -Breaking each manager into a trait per operation lets every provider declare exactly the dependencies its one method needs. The user manager becomes three fine-grained components: - -```rust -#[cgp_component(UserCreator)] -pub trait CanCreateUser { - fn create_user(&self, username: &str, email: &Email) -> Result; -} - -#[cgp_component(UserGetter)] -pub trait CanGetUser { - fn get_user(&self, user_id: &UserId) -> Result; -} - -#[cgp_component(UserUpdater)] -pub trait CanUpdateUser { - fn update_user_data(&self, user_id: &UserId, user_data: &UserData) -> Result<(), Error>; -} -``` - -The post manager splits the same way into `PostCreator`, `PostGetter`, `PostUpdater`, and `PostDeleter`. Each operation now has its own provider, and the providers that do not need a content check no longer mention one. `GetUserWithPostgres` reads only the database; `CreateUserWithPostgres` adds nothing else either, because — as the next step shows — the censoring will move out of it entirely: - -```rust -#[cgp_impl(new CreateUserWithPostgres)] -impl UserCreator { - fn create_user( - &self, - #[implicit] database: &PostgresDb, - username: &str, - email: &Email, - ) -> Result { - // ... - } -} - -#[cgp_impl(new GetUserWithPostgres)] -impl UserGetter { - fn get_user(&self, #[implicit] database: &PostgresDb, user_id: &UserId) -> Result { - // ... - } -} -``` - -Splitting the traits also makes capability isolation possible: because deleting a post is its own `PostDeleter` component, code that should only read and write posts can be given the getter and updater without ever receiving the destructive `delete_post`. The price is that there are now seven CRUD components plus two content-safety ones to wire, where before there were four. - -## Lifting the filter into a higher-order provider - -With creation isolated in its own trait, the username check no longer belongs inside the database provider — it can become a separate provider that wraps any user creator. `FilterCensoredUsername` is a [higher-order provider](../concepts/higher-order-providers.md): it takes an inner `UserCreator` as a type parameter, runs the censor check, and forwards to the inner provider only if the name is allowed: - -```rust -#[cgp_impl(new FilterCensoredUsername)] -#[uses(CanCensorUsername)] -#[use_provider(InnerCreator: UserCreator)] -impl UserCreator { - fn create_user(&self, username: &str, email: &Email) -> Result { - if self.username_is_censored(username) > Probability::new(0.8) { - return Err(Error::InvalidUsername); - } - - InnerCreator::create_user(self, username, email) - } -} -``` - -The [`#[use_provider]`](../reference/attributes/use_provider.md) attribute is what keeps the inner type readable: `InnerCreator: UserCreator` declares that `InnerCreator` must itself be a user-creation provider for this context, and `InnerCreator::create_user(self, …)` dispatches through it. `FilterCensoredUsername` knows nothing about databases, and `CreateUserWithPostgres` now knows nothing about censoring — the two compose only when wired together as `FilterCensoredUsername`. Because the wrapper is generic, the same filter applies to any creator: a SQLite-backed `CreateUserWithSqlite`, were one added, would gain censoring through `FilterCensoredUsername` with no new code. The post side gets the matching `FilterSpamMessage` wrapper around any post creator. - -## Grouping providers into bundles - -A [`delegate_components!`](../reference/macros/delegate_components.md) table with `new` defines a standalone provider — a bundle — whose only job is to hold a sub-table other contexts can reuse as a unit. Grouping the user providers, the post providers, and the AI-backed content filters each into their own bundle keeps related wiring together: - -```rust -delegate_components! { - new PostgresUserComponents { - UserCreatorComponent: - FilterCensoredUsername, - UserGetterComponent: - GetUserWithPostgres, - UserUpdaterComponent: - UpdateUserWithPostgres, - } -} - -delegate_components! { - new PostgresPostComponents { - PostCreatorComponent: - FilterSpamMessage, - PostGetterComponent: - GetPostWithPostgres, - PostUpdaterComponent: - UpdatePostWithPostgres, - PostDeleterComponent: - DeletePostWithPostgres, - } -} - -delegate_components! { - new AiContentFilterComponents { - UsernameCensorComponent: - AiUserCensor, - SpamMessageDetectorComponent: - AiSpamMessageDetector, - } -} -``` - -The top-level context then forwards each component to the bundle that owns it. Using the array-key form of `delegate_components!`, several keys can share one bundle in a single entry: - -```rust -delegate_components! { - ProductionApp { - [ - UserCreatorComponent, - UserGetterComponent, - UserUpdaterComponent, - ]: - PostgresUserComponents, - [ - PostCreatorComponent, - PostGetterComponent, - PostUpdaterComponent, - PostDeleterComponent, - ]: - PostgresPostComponents, - [ - UsernameCensorComponent, - SpamMessageDetectorComponent, - ]: - AiContentFilterComponents, - } -} -``` - -The bundles read cleanly on their own, but the top-level table still has to spell out every component name to route it to a bundle. The grouping exists in the bundle definitions, yet the context cannot refer to "all the user components" as one thing — it must list them. - -## Grouping component keys with namespaces - -A [namespace](../concepts/namespaces.md) gives a group of components a shared key, so a context can route the whole group with one entry instead of listing each name. A component joins a namespace under a dotted path with the [`#[prefix(...)]`](../reference/macros/cgp_component.md) attribute on its trait; here the three user components all register under `@app.core.user` in the built-in `DefaultNamespace`: - -```rust -#[cgp_component(UserCreator)] -#[prefix(@app.core.user in DefaultNamespace)] -pub trait CanCreateUser { - fn create_user(&self, username: &str, email: &Email) -> Result; -} - -#[cgp_component(UserGetter)] -#[prefix(@app.core.user in DefaultNamespace)] -pub trait CanGetUser { - fn get_user(&self, user_id: &UserId) -> Result; -} - -#[cgp_component(UserUpdater)] -#[prefix(@app.core.user in DefaultNamespace)] -pub trait CanUpdateUser { - fn update_user_data(&self, user_id: &UserId, user_data: &UserData) -> Result<(), Error>; -} -``` - -The post components register under `@app.core.post` the same way, and the two content-safety components under `@app.extra.content_filter`. A context opts into the namespace with a `namespace` header line in its delegation table, after which a path key stands in for every component registered under it: - -```rust -delegate_components! { - ProductionApp { - namespace DefaultNamespace; - - @app.core.user: PostgresUserComponents, - @app.core.post: PostgresPostComponents, - @app.extra.content_filter: AiContentFilterComponents, - } -} -``` - -The `namespace DefaultNamespace;` line tells `ProductionApp` to resolve lookups through the namespace, and `@app.core.user: PostgresUserComponents` forwards every component sitting under that path to the bundle in one entry. The top-level table now has three lines regardless of how many operations the user and post domains grow to, because adding a method means adding a `#[prefix]`-tagged component to an existing path, not a new line at the top level. Listing the bundled components in a [`check_components!`](../reference/macros/check_components.md) block confirms the namespace wiring actually resolves each one: - -```rust -check_components! { - ProductionApp { - UserCreatorComponent, - UserGetterComponent, - UserUpdaterComponent, - PostCreatorComponent, - PostGetterComponent, - PostUpdaterComponent, - PostDeleterComponent, - UsernameCensorComponent, - SpamMessageDetectorComponent, - } -} -``` - -## Hierarchical grouping and context variants - -Because the prefixes form a hierarchy, a parent path can group its children, so the wiring can be collapsed another level. Both `@app.core.user` and `@app.core.post` sit under `@app.core`, which lets an intermediary bundle gather all the core CRUD wiring behind a single path, with the extras gathered the same way: - -```rust -delegate_components! { - new PostgresCoreComponents { - namespace DefaultNamespace; - - @app.core.user: PostgresUserComponents, - @app.core.post: PostgresPostComponents, - } -} - -delegate_components! { - new ProductionExtraComponents { - namespace DefaultNamespace; - - @app.extra.content_filter: AiContentFilterComponents, - } -} -``` - -The top-level context drops to two entries — its essential core behavior and its swappable extras: - -```rust -delegate_components! { - ProductionApp { - namespace DefaultNamespace; - - @app.core: PostgresCoreComponents, - @app.extra: ProductionExtraComponents, - } -} -``` - -The real payoff is that contexts now differ at a glance. A test context reuses the same PostgreSQL-backed core but swaps the AI content filters for dummy ones, so its full definition makes the single difference obvious: - -```rust -delegate_components! { - new DummyExtraComponents { - namespace DefaultNamespace; - - @app.extra.content_filter: DummyContentFilterComponents, - } -} - -delegate_components! { - TestApp { - namespace DefaultNamespace; - - @app.core: PostgresCoreComponents, - @app.extra: DummyExtraComponents, - } -} -``` - -`ProductionApp` and `TestApp` share `@app.core` and differ only in `@app.extra`, so a reader sees immediately that the two run identical core logic and diverge only in their content filtering — a comparison that, with the flat per-component table, would mean scanning a dozen entries that may not even appear in the same order. A local-first variant would follow the same shape, keeping the production extras but pointing `@app.core` at an SQLite core bundle, and the one swapped line would again be the whole story. diff --git a/docs/guides/README.md b/docs/guides/README.md deleted file mode 100644 index e86d2979..00000000 --- a/docs/guides/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# CGP Guides - -This directory holds the *guides* to writing Context-Generic Programming code — documents that direct the **choices** an author makes, rather than explaining what a construct is. Where the [reference](../reference/README.md) tells you what a construct means and the [concepts](../concepts/README.md) explain the ideas that tie constructs together, a guide answers the question that comes up once you already understand the pieces: *given several ways to express something, which should I use, and how do I evolve code from one form to another?* - -## How guides differ from concepts, reference, and examples - -The four sections answer four different questions, and a reader in a hurry can pick the one that matches their need. A [reference document](../reference/README.md) answers "what does `#[prefix]` mean and what does it expand to?" A [concept document](../concepts/README.md) answers "what is a namespace, as an idea?" An [example](../examples/README.md) answers "show me a namespace solving a real problem end to end." A **guide** answers "my `delegate_components!` table has grown unwieldy — what should I do about it, and in what order?" A guide is prescriptive: it recommends a default, names the trade-offs of the alternatives, and often walks a concrete before/after refactoring so the recommendation is grounded in real code rather than stated in the abstract. - -A guide leans on the other three rather than restating them. It links to the reference for the exact syntax of each construct it recommends, to the concepts for the mechanism behind a recommendation, and to the examples for a fuller worked scenario. Keep the guide focused on the decision and the migration path; when a guide finds itself explaining what a construct *is* at length, that explanation belongs in the reference or a concept, linked from the guide. - -## The catalog - -The authoring rules for these documents live in [../AGENTS.md](../AGENTS.md). Each guide below names a decision you face when writing CGP and walks through how to make it; the [Summary](#summary) at the end condenses all of them into one cheat-sheet, so read it first for the recommendations and follow a link for the full before/after mapping and the rules. - -- [Writing providers](writing-providers.md) — `#[cgp_impl]` in consumer-trait shape, omitting the context parameter, instead of the inside-out provider forms. -- [Declaring a provider's dependencies](declaring-dependencies.md) — `#[uses]` and `#[use_provider]` instead of hand-written `where` bounds. -- [Reading context fields](reading-context-fields.md) — `#[implicit]` arguments instead of getter traits. -- [Importing abstract types](importing-abstract-types.md) — `#[use_type]` aliases and the concrete-type equality form instead of a supertrait plus `Self::Type`. -- [Adding capability supertraits](capability-supertraits.md) — `#[extend]` instead of native `:` supertrait syntax. -- [Dispatching a component per type](dispatching-per-type.md) — the `open` statement or a namespace instead of a `UseDelegate` table. -- [Organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md) — keeping a growing `delegate_components!` table short with path prefixes, namespaces, and per-type defaults, worked as a refactoring of a real application. -- [Debugging CGP compile errors](debugging.md) — reaching for [`cargo-cgp`](../reference/cargo-cgp.md) first (it traces the cause for you), then tracing a wiring failure by hand as the fallback: reading the error's shape, moving the error to the wiring site with checks, reducing to a minimal reproduction, inspecting the macro expansion, and a decoder for the errors you actually see. - -## Summary - -This section condenses every guide above into one quick reference. Read it for the recommendation and the reason; follow a link when you need the full before/after mapping, the corner cases, or a worked example. - -**Write CGP that looks like ordinary Rust.** The explicit forms — an inside-out provider-trait `impl`, `where`-clause dependencies, `::Type` abstract types, `UseDelegate` dispatch tables — are exactly what the macros desugar to, so you keep reading them in generated code and older codebases, but you should *write* the vanilla-looking form in all new code and reach for an explicit form only when a construct genuinely cannot express the case. Each row below is one such shift: - -| When you… | Prefer | Instead of | -|---|---|---| -| write a provider ([guide](writing-providers.md)) | `#[cgp_impl]` with the header `impl Trait` (omit `for Context`, keep `self`/`Self`) | raw `#[cgp_provider]`/`#[cgp_new_provider]` in inside-out shape | -| require a capability or an inner provider ([guide](declaring-dependencies.md)) | `#[uses(Trait)]` / `#[use_provider(P: Trait)]`, comma-separated in one attribute | hand-written `Self:`/`P: Trait` `where` bounds | -| read a value from the context's own field ([guide](reading-context-fields.md)) | an `#[implicit]` argument | a getter trait declared only to read it | -| name an abstract type ([guide](importing-abstract-types.md)) | `#[use_type(Trait.Type)]` + the bare alias (`Trait.Type in Context` for a foreign type, `{Type = Concrete}` to pin one) | a `: Trait` supertrait + qualified `Self::Type`, or `where Context: Trait` + `Context::Type` | -| add a non-type capability supertrait ([guide](capability-supertraits.md)) | `#[extend(Trait)]` | native `: Supertrait` inheritance syntax | -| dispatch a generic-parameter component per type ([guide](dispatching-per-type.md)) | the `open` statement or a [namespace](namespaces-and-prefixes.md) | `#[derive_delegate]` + a `UseDelegate` nested table | - -**When an explicit form is still right.** Keep a hand-written `where` clause for an associated-type-equality bound on a trait you would not `#[use_type]` from (`Iterator`, `From`) — but move an *abstract-type* pin like `Self: HasErrorType` into the `#[use_type]` equality form. Name the context explicitly (`impl Trait for Context`) only for a lifetime or higher-ranked bound the sugar cannot carry, or reach for `#[cgp_getter]` only when a context must choose per-wiring which field a getter reads. And a construct's own local associated type stays qualified as `Self::Output` always — it is never a `#[use_type]` import. - -**Keep a growing wiring table short with namespaces and prefixes** ([guide](namespaces-and-prefixes.md)). As component counts rise, group components under path prefixes with `#[prefix(@path in DefaultNamespace)]`, lift a backend's provider choices into a reusable namespace (via `#[default_impl]` or namespace body entries) so a context joins it with one `namespace N;` line, and pull a separate concern's table in with a `for … in` loop. Two rules bite: a context can only override a path its namespace routes *to* but does not itself register, and a prefixed component's `#[default_impl]` must live in the namespace's own crate. - -**When a wiring fails to compile, run [`cargo-cgp`](../reference/cargo-cgp.md) first — it traces the cause for you — and trace by hand only as a fallback** ([guide](debugging.md)). CGP wiring is lazy, so one broken link surfaces as many errors on distant lines naming generated types; when you do read the raw output yourself, read the failing *trait* (an unmet `IsProviderFor` means a dependency is missing, a failed `DelegateComponent` means the lookup has no entry), move the error to the wiring site with `check_components!`, and when a large program puzzles you, reduce it to the smallest reproduction — or snapshot the expansion — rather than reasoning about coherence on paper. diff --git a/docs/guides/capability-supertraits.md b/docs/guides/capability-supertraits.md deleted file mode 100644 index fbf30990..00000000 --- a/docs/guides/capability-supertraits.md +++ /dev/null @@ -1,34 +0,0 @@ -# Adding capability supertraits - -A CGP component often depends on another capability, and this guide is about declaring that dependency with `#[extend]`, which reads as importing a capability, rather than the native `:` supertrait syntax, which reads as inheritance. - -This guide is the capability counterpart to [importing abstract types](importing-abstract-types.md), which handles a supertrait whose *type* the signature names. - -## Add supertraits with `#[extend]`, not native `:` syntax - -Add a non-type capability supertrait to a [`#[cgp_component]`](../reference/macros/cgp_component.md) trait with [`#[extend(...)]`](../reference/attributes/extend.md), rather than writing the native `pub trait CanDoX: Supertrait` form. Both produce the same trait with the same supertrait, but the attribute reads as an import — a capability the trait re-exports — which matches how CGP actually uses supertraits: as declared dependencies, not as a base class. Native `:` supertrait syntax tends to read as inheritance to programmers coming from object-oriented languages, suggesting an is-a relationship to a parent that a CGP component does not have. `#[extend(...)]` avoids that misreading and pairs symmetrically with [`#[uses(...)]`](declaring-dependencies.md): `#[uses]` imports a capability for the implementation's private use, `#[extend]` re-exports one as part of the trait's public contract. The native form: - -```rust -#[cgp_component(Greeter)] -pub trait CanGreet: HasName { - fn greet(&self) -> String; -} -``` - -becomes: - -```rust -#[cgp_component(Greeter)] -#[extend(HasName)] -pub trait CanGreet { - fn greet(&self) -> String; -} -``` - -`#[extend]` is the tool for a supertrait that contributes only a *capability* — like `HasName` here, which `CanGreet` depends on but whose value it reads through the getter rather than naming an abstract type in the signature. When the supertrait is instead an **abstract-type component** whose associated type the signature does name, use [`#[use_type]`](importing-abstract-types.md) instead: `#[use_type]` adds the supertrait *and* rewrites the bare type, which `#[extend]` does not, so it is the recommended form for abstract-type components. In [`#[cgp_fn]`](../reference/macros/cgp_fn.md), whose `where` clauses are impl-side dependencies rather than supertraits, `#[extend]` is the only way to declare a supertrait at all. - -## Related guides - -- [Importing abstract types](importing-abstract-types.md) — use `#[use_type]` instead when the supertrait carries an associated type the signature names. -- [Declaring dependencies](declaring-dependencies.md) — the `#[uses]` counterpart for a capability the implementation uses privately rather than re-exporting. -- [Guides summary](README.md#summary) — the cheat-sheet across all the guides. diff --git a/docs/guides/debugging.md b/docs/guides/debugging.md deleted file mode 100644 index d55adf86..00000000 --- a/docs/guides/debugging.md +++ /dev/null @@ -1,112 +0,0 @@ -# Debugging CGP compile errors - -A broken CGP wiring rarely fails where it is wrong — it fails wherever the wiring is finally used, often as a wall of errors naming deeply nested types; this guide is the playbook for tracing such an error back to its cause. The fastest path, though, is usually not to trace it by hand at all: **run [`cargo-cgp`](../reference/cargo-cgp.md) first** — it does this tracing for you — and treat the hand techniques in this guide as the fallback for when it is unavailable or leaves an error unrewritten. - -## Why CGP errors are shaped the way they are - -The first thing to internalize is that CGP wiring is resolved *lazily*, so a single missing or wrong entry does not error at the entry — it errors at every place that transitively needs it. A context assembles its behavior from a table of [`DelegateComponent`](../reference/traits/delegate_component.md) entries, and the compiler only checks that a provider's dependencies are met when something actually calls the consumer trait. One unsatisfied dependency deep in the graph therefore surfaces as a cascade of failures on unrelated-looking lines, and the *count* of errors tells you nothing about the number of mistakes — a dozen errors are usually one broken link seen from a dozen angles. Fix the first root cause and the cascade collapses. - -The second thing is that the errors quote *generated* code, so they name types you never wrote. A component's wiring runs through [`IsProviderFor`](../reference/traits/is_provider_for.md), `DelegateComponent`, [`CanUseComponent`](../reference/traits/can_use_component.md), `RedirectLookup`, and the type-level `PathCons`/`Symbol`/`Chars` spines, and a failure prints these in full. The nested types are noise until you learn to read past them to the one trait and the one context that actually failed. The techniques below are all ways to cut through that noise: read the error's shape, move the error to where the mistake is, or shrink the program until the mistake is the only thing left. - -## Reach for cargo-cgp first - -Before applying any technique in this guide, reach for [`cargo-cgp`](../reference/cargo-cgp.md) — CGP's first-class error toolchain — because it automates by machine exactly what the rest of this guide does by hand. Run `cargo cgp check` in place of `cargo check`: it turns on the next-generation trait solver, which *un-hides* the buried dependency errors the default solver suppresses, and it rewrites the classes it recognizes into a compact, root-cause-first form — a `[CGP-Exxx]` headline over a `cargo tree`-style dependency chain that names the real cause. For the common wiring mistakes — a missing field, an unwired component, an unmet dependency — the cause you would otherwise trace through a cascade is already at the top of the output. Install and run it per the [cargo-cgp reference](../reference/cargo-cgp.md). - -**The rest of this guide is the fallback.** Reach for the hand techniques below when `cargo-cgp` is not available on the machine, when it leaves an error largely as `rustc` wrote it — it is an early pre-release (v0.1.0-alpha) that reshapes the core classes but not yet every one, so an orphan-rule error, for instance, still comes through raw — or when you want to understand the raw diagnostic behind a reshaped one. In every such case the shape-reading, grepping, check-promotion, and reduction moves that follow still apply to the underlying compiler output, which is the same output `cargo-cgp` starts from. - -## Read the error's shape before its contents - -Most CGP errors are one of a few shapes, and recognizing the shape tells you what kind of mistake you are looking for before you decode a single nested type. Learn to read the *trait* in the error and ignore the type arguments on a first pass. - -An unsatisfied **`IsProviderFor`** bound means "this provider is not a valid provider for this component, because one of its own dependencies is missing." The macros attach `IsProviderFor` to every provider under the same `where` clause the provider needs, precisely so that a missing transitive requirement is named here rather than swallowed. When you see `SomeProvider: IsProviderFor` is not satisfied, read it as "`MyContext` is missing something `SomeProvider` needs to implement `Foo`," and look at the provider's dependencies for the one the context does not supply. - -An unsatisfied **`DelegateComponent`** or **`Namespace
`** bound means the *lookup* failed: the context (or namespace) has no entry for that key. A `CanUseComponent` failure is the same thing one level up — the check assertion that a context can actually use a component — and its cause is always further down the note chain, at the `IsProviderFor` or `DelegateComponent` that really failed. - -One error shape is especially worth recognizing: **"the trait `X` is not implemented for `T`" immediately followed by a `help:` note saying "the trait `X` *is* implemented for `T`"** (often with a slightly different generic parameter name). This near-contradiction means an impl exists but a nested requirement inside it is unmet, or two candidate impls make the choice ambiguous — the compiler found the impl but could not commit to it. Do not trust the `help:`; the real cause is a bound the impl carries that does not hold. That exact shape can flag, for instance, a namespace-registration impl whose leaked `where` clause demands `PathCons<..>: HasErrorType`, a bound that can never be satisfied. - -When a type in the error is elided as `...`, the full form is written to a file the compiler names in a final note (`the full name for the type has been written to '….long-type-….txt'`). Reading that file is how you recover *which* path or *which* context the error is really about — the elided middle is frequently the one segment that reveals the mistake, as when a dependency's context parameter turns out to be a `PathCons<..>` path rather than the real context. - -## Grep for the suspected line instead of reading the whole log - -When you already suspect what went wrong, grep the captured error output for the one line that would confirm it rather than reading the cascade top to bottom. A CGP failure routinely runs to hundreds of lines — one mistake repeated at every dependent provider, each block naming generated types you never wrote — so reading all of it to recover a fact you could have searched for wastes both effort and context. Capture the build output to a file first, targeting the smallest failing unit so the cascade does not multiply across crates, then work the file with `grep -n` so every match carries a line number you can open directly: - -```bash -cargo check -p 2>&1 | tee /tmp/cgp-error.txt # or a single --test / example -``` - -This is the cheap alternative to handing the whole log to a sub-agent: grep when a targeted search answers your question, and [delegate](../skills/cgp/references/error-extraction.md) only when it will not. It pays off most when your suspicion is right — a field you think you forgot, a key you think you wired twice, a cycle you think you introduced — because then a single grep either confirms the cause and points at the fix, or rules it out and redirects you, for the price of a few lines. - -Grep the error headlines first, because that one search classifies everything. `grep -nE '^error' /tmp/cgp-error.txt` prints one line per error block — the code and the trait it names — which is the whole of [reading the error's shape](#read-the-errors-shape-before-its-contents) recovered without scrolling. From the headlines alone you know whether you face a dependency error (`E0277`/`E0599`), a coherence conflict (`E0119`), a cycle (`E0275`), an orphan violation (`E0210`), an unconstrained generic (`E0207`), or a name-resolution failure (`E0576`/`E0425`), and the [decoder below](#a-decoder-for-the-errors-you-will-actually-see) says what each means. Pair it with the tail of the file (`tail -n 3`), which shows the error count and any `the full name for the type has been written to '…long-type-….txt'` note telling you a cause is hiding behind an elided `...`. - -Then grep one class-specific pattern to confirm the cause, because each class has a signature line the headline points you to. Once the headline names the class, the second grep either finds the cause or proves it absent: - -| To confirm | Grep for | What the hit tells you | -|---|---|---| -| a surfaced dependency leaf (missing field or capability) | `grep -n 'help:'` | the concrete unmet bound; a `HasField>>` spells the field name letter-by-letter on that one line, and the paired "but trait `HasField<…>` *is* implemented" hint names the field the context *does* have | -| an unwired component | `grep -n 'does not contain any DelegateComponent entry'` | CGP's own diagnostic message, naming the component with no wiring | -| a duplicate key or generated name (`E0119`/`E0428`) | `grep -n 'conflicting implementation\|defined multiple times'` | the two carets are the two entries to reconcile | -| whether an `E0119` is a specific override or a blanket forwarding overlap | `grep -n 'downstream crates may implement'` | present → a duplicate key or a namespace override on a concrete key; absent on a fully-generic `DelegateComponent<_>` → two namespaces (or a namespace and a bare-key `for` loop) joined | -| a wiring or namespace-inheritance cycle (`E0275`) | `grep -n 'overflow evaluating'` | the recursing requirement names the loop; ignore the `recursion limit` help, which never applies to a true cycle | -| an orphan-rule violation (`E0210`/`E0117`) | `grep -n 'must be used as the type parameter'` | a registration into a foreign namespace with no local type | -| an unconstrained entry generic (`E0207`) | `grep -n 'is not constrained'` | the caret sits on the `` that must reach the key | -| an ill-formed lowered type (`E0277` `Sized`) | `grep -n 'size for values of type\|Sized'` | a field- or argument-type shorthand combination with no lowering rule (e.g. `Option<&[T]>`) | -| a `#[use_type]` name or context typo (`E0576`/`E0425`) | `grep -n 'cannot find associated type\|cannot find type'` | the caret sits on the misspelled name | - -The leaf is not always in a `help:` note: for an [unsatisfied ordinary trait bound](../errors/checks/ordinary-trait-bound.md) (`f64: Eq`) and an [unregistered namespace path](../errors/checks/unregistered-namespace-path.md) (`PathCons<..>: DefaultNamespace`) the concrete cause *is* the `^error` headline itself, so the first grep already carries it. A `PathCons>` key, like a field `Symbol`, spells its path segments out on one line, so reading that single hit decodes the unbound path. - -One shape defeats grep entirely, and recognizing it saves a fruitless search. If the headline grep returns only an `E0599` "method … exists … but its trait bounds were not satisfied" (or an `E0277` on a bare consumer trait) with no `CanUseComponent`/`IsProviderFor` block beneath it, you are looking at a [hidden dependency](../errors/hidden/unsatisfied-dependency.md): the root cause is *absent from the output*, not merely far down it, so no grep can find it. Do not keep searching — [promote the error with a check](#move-the-error-to-where-the-mistake-is-with-checks) and re-run, which turns it into a surfaced `E0277` whose leaf the greps above do find. - -Spawn a sub-agent instead of grepping when the search will not converge. Grep is the right tool when you have a hypothesis to test or a single fact to pull from a class you have already identified; hand the whole log to a sub-agent, per the [error-extraction skill](../skills/cgp/references/error-extraction.md), when the headline grep shows several unrelated classes tangled together, when the cause hides behind an elided `...` that forces you to cross-reference the `long-type-….txt` file and decode a deep `PathCons` spine, or when you have no hypothesis and the cascade is large. The dividing line is whether you know what you are looking for: a targeted question is a grep, an open-ended read is a delegation. - -## Move the error to where the mistake is with checks - -Because a lazy failure surfaces far from its cause, the most reliable first move is to force the check *at the wiring site* with [`check_components!`](../reference/macros/check_components.md). A standalone check trait asserts `CanUseComponent` for each component you name, so a missing dependency errors on the checked component's line — walking through `IsProviderFor` to name the real cause — instead of at some distant call site. Adding a check for the component you suspect turns a confusing downstream error into one anchored at the wiring you control. The [check-traits concept](../concepts/check-traits.md) explains why this works. - -For a [higher-order provider](../concepts/higher-order-providers.md) — a provider wrapping another provider — a plain check tells you the stack is broken but not which layer, though its diagnostic shape already leans one way: an inner-layer failure runs the `required for …` chain through both providers' `IsProviderFor` (with a `redundant requirement hidden` note), while an outer-layer failure stops at the outer provider and never names the inner one, as the [higher-order provider layer failure](../errors/checks/higher-order-provider-layer.md) class details. To force the boundary rather than read it, the `#[check_providers(...)]` attribute changes the assertion from `CanUseComponent` on the context to `IsProviderFor` on each named provider, so a dependency missing only from the outer wrapper errors on its line alone while one missing from the inner provider errors on both, pinpointing the layer. When a component has generic parameters, check it with concrete ones (`FooComponent: (Rectangle, f64)`) so the check actually instantiates the wiring you doubt rather than leaving it generic and unchecked. - -## Reduce the failure to a minimal reproduction - -The single most effective technique, and the one to reach for before theorizing, is to reproduce the symptom in the smallest self-contained program you can. A large wiring cross-cuts, so a mistake in it produces entangled errors; the same mistake in a ten-line module produces one. Copy the failing construct into a fresh test file, strip everything the symptom does not need, and confirm it still fails. If it stops failing as you strip, the last thing you removed is implicated; if it keeps failing, you now have a tractable case to reason about. - -Two habits make this fast. Build the reproduction *up* rather than *down* when you are unsure what triggers it: start from a version that compiles and add one variable at a time — one dependency, one generic parameter, one namespace entry — until it breaks, so the last addition is the trigger. And when two forms *should* behave identically but one fails, put both in one file as separate modules and compile once; a side-by-side that isolates the single difference is worth more than any amount of reasoning about why they "must" be the same. Reasoning about Rust's coherence and trait resolution on paper is unreliable enough that a compile-and-observe loop almost always settles the question faster than argument. - -A scratch module or throwaway test is the natural place for this — it compiles quickly, and a compile-time wiring check needs no assertions to be a test, since successful compilation *is* the pass. Delete the scratch once it has told you what you need, or promote it to a regression test if it pins a fixed bug. - -## Inspect what the macro actually emitted - -When a reproduction still puzzles you, stop guessing about the expansion and look at it. The wiring failure is always "the emitted impls do not resolve," and you cannot reason about why until you see the impls. [`cargo cgp expand`](../reference/cargo-cgp.md#expanding-the-generated-code) is the way to see them: it prints the crate after macro expansion with CGP's type-level constructs resugared, so a key that the error rendered as a `Symbol<6, Chars<'h', …>>` spine reads `Symbol!("height")` here. Narrow it to the construct in question rather than reading a whole crate — naming a provider trait gives you every impl of it, which is exactly "what did this component generate": - -```sh -cargo cgp expand --lib --item AreaCalculator # a trait: its definition and every impl of it -cargo cgp expand --lib --item contexts::MockApp # a type: its HasField impls and its wiring entries -``` - -Reading the generated `where` clauses and the exact key types is often enough to spot the defect — a bound on the wrong type, a path with a segment too many, a parameter that should not be there. Where `cargo cgp expand` is unavailable, plain `cargo expand` shows the same expansion without the resugaring; and in a project set up with the CGP test utilities, the `snapshot_*!` helpers in `cgp-macro-test-util` (`snapshot_cgp_impl!`, `snapshot_delegate_components!`, `snapshot_cgp_namespace!`, …) additionally emit the real generated code into the module *and* pretty-print it into an inline snapshot you can review and keep. - -Comparing two expansions side by side localizes a difference precisely. When one form works and another does not, expand both and diff the output; the delta is the bug. This is how a `#[default_impl]` defect can be pinned: the namespace-registration impl it emitted carried a `where` clause that the equivalent body entry did not, and seeing the two expansions next to each other made the leaked clause obvious. Capturing the expansion as a snapshot doubles as a permanent regression test, so the effort is not throwaway. - -## Bisect a wiring table - -For a large `delegate_components!` or `cgp_namespace!` table that fails as a whole, bisect it. Comment out entries until it compiles, then add them back a few at a time; the entry that reintroduces the failure is the culprit, or interacts with one that is. Because the entries are mostly independent, this converges quickly, and it works even when the error message points nowhere useful. The same approach applies to a context that joins a namespace *and* adds its own entries: reduce the context to just the `namespace N;` join first to confirm the namespace alone resolves, then add the `for` loops and overrides back one at a time to find which addition conflicts. - -## A decoder for the errors you will actually see - -A handful of specific compiler errors recur in CGP code, and each maps to a small set of causes. Use this as a lookup once you have the error code and the trait it names. - -- **`E0599` "method exists but its trait bounds were not satisfied"** — the *hidden* form of an unsatisfied dependency: a consumer-trait method called on a context whose wiring will not resolve. It names the consumer and provider traits but **hides** the dependency that actually failed, because the compiler drops the nested bound when a blanket impl sits among the candidate impls. Do not scan it for a root cause — there is none in it; promote it with a [`check_components!`](../reference/macros/check_components.md) to surface one. See [Unsatisfied dependency (hidden)](../errors/hidden/unsatisfied-dependency.md). -- **`E0277` on `IsProviderFor<…>`, `CanUseComponent<…>`, or a consumer trait** — a dependency is unmet or a component is unwired. Either the context has no `DelegateComponent` entry for the component (wire it), or the chosen provider needs something the context does not supply (read the note chain to the innermost failing bound and satisfy it, or add a [`check_components!`](../reference/macros/check_components.md) to name it). A bound whose *key* is a `PathCons<..>` path — `PathCons<..>: DefaultNamespace` or `Ctx: DelegateComponent>` unsatisfied — means a namespace redirect landed on a path no entry binds; bind a provider there (a `#[default_impl]`, a namespace body entry, or a direct `@path:` line), per [Unregistered namespace path](../errors/checks/unregistered-namespace-path.md). A bound like `SomeType: IsProviderFor<…>` where `SomeType` looks like a `RedirectLookup` means the lookup instead resolved to a delegate that is not actually a provider for that component. The surfaced (checked) form names the real bound in a `help:` note — see [Check-trait failure](../errors/checks/check-trait-failure.md); a repeated wall of these over a single cause is a [verbose cascade](../errors/checks/verbose-cascade.md). When the unmet leaf is an *ordinary* trait (`f64: Eq`, `T: Clone`) on an abstract type or impl generic rather than a `HasField` — its own primary `E0277` with a `help:` list of conforming standard types — the fix is to satisfy or relax that trait, not to wire a component; see [Unsatisfied ordinary trait bound](../errors/checks/ordinary-trait-bound.md). -- **`E0119` conflicting implementation of `DelegateComponent<…>` (or a namespace trait)** — two impls overlap for one key. The plain case is the same key *declared twice*: two `delegate_components!` entries for one key, two `cgp_namespace!` entries for one path, an `open` header colliding with a mapping — remove one (see [Conflicting wiring](../errors/wiring/conflicting-wiring.md)). Two namespace-specific cases have their own remedies: a *fully-generic* `DelegateComponent<_>`/`IsProviderFor<_, _, _>` conflict with no downstream note means two blanket forwardings overlap (joining two namespaces, or a namespace join plus a bare-key `for` loop) — inherit rather than join, or move the loop key into a path (see [Overlapping namespace forwarding](../errors/wiring/namespace-forwarding-conflict.md)); a conflict on a *concrete* key means an entry tries to override a path or key the namespace already claims (a context re-wiring a registered path, or a child namespace redefining an inherited entry) — override only a path the namespace leaves unbound (see [Namespace override conflict](../errors/wiring/namespace-override-conflict.md)). -- **`E0210`/`E0117` orphan-rule violation on a generated impl** — a namespace registration is being written for a foreign trait and foreign types. A `#[default_impl]` for a *prefixed* component expands to `impl Namespace for PathCons<..>` (all foreign), an unprefixed `#[default_impl(ForeignComponent in ForeignNamespace)]` to `impl Namespace for ForeignComponent`, and a `cgp_namespace!` block without `new` re-opening a foreign namespace to `impl ForeignNamespace for Key` — none of which a downstream crate can write. Register from the crate that owns the namespace, key on a local component the crate owns, use a namespace body entry, or inherit the namespace into a new local one (see [`DefaultNamespace`](../reference/traits/default_namespace.md)). See [Orphan-rule violation](../errors/wiring/orphan-rule.md). -- **`E0275`/overflow evaluating a requirement** — a resolution cycle. The classic case is delegating a component to [`UseContext`](../reference/providers/use_context.md) when the context's only implementation of that component *is* that delegation, so the lookup chases its own tail; break it by wiring the component to a concrete provider. This form surfaces only when *forced through a check* (a plain method call hides it as the `E0599` above) — see [Wiring cycle](../errors/wiring/wiring-cycle.md). A second form is a circular namespace parent chain (`cgp_namespace! { new A: B }` with `new B: A`, or `new A: A`), which overflows *eagerly at the `cgp_namespace!` definitions* — see [Namespace inheritance cycle](../errors/wiring/namespace-inheritance-cycle.md). For either, ignore the `help: increase the recursion limit` suggestion: a true cycle never terminates, so no limit clears it. -- **`E0207` unconstrained type parameter on a generated impl** — a generic parameter appears only in an associated-type position and so does not constrain the impl. This shows up when a *generic* provider is registered as a per-type default, since the provider's parameter lands only in the `Delegate` type; register a concrete provider instead. See [Unconstrained generic](../errors/wiring/unconstrained-generic.md). -- **`E0277` "the size for values of type `…` cannot be known at compilation time" on a getter or implicit-argument macro** — not a wiring error but a *lowering* one: a field- or argument-type shorthand combination the macro has no rule for (most often `Option<&[T]>`) was lowered literally into a bound naming an unsized type. Change the field type to a shape a single shorthand supports, or write the accessor by hand. See [Ill-formed generated type](../errors/lowering/ill-formed-generated-type.md). -- **A dependency demanded of a `PathCons<..>` path rather than the context** — a bound like `PathCons<..>: HasErrorType` means a `Self`-keyed impl-side bound leaked onto an impl whose `Self` is a path key. This is a macro-level defect rather than a wiring mistake; capture it as a minimal reproduction and check the emitting macro's lowering. - -## Related documentation - -- [cargo-cgp](../reference/cargo-cgp.md) — CGP's error toolchain, the recommended first move this guide defers to; install and run it, and fall back to the hand techniques here only when it cannot help. -- [Error catalog](../errors/README.md) — the reference companion to this guide: one document per class of post-codegen error, recording the diagnostic's shape, whether the root cause is present, and where it sits. Where this guide is the tracing playbook, the catalog is the per-class reference it indexes into — including the [hidden unsatisfied-dependency](../errors/hidden/unsatisfied-dependency.md) class, whose diagnostic omits the root cause entirely. -- [Check traits](../concepts/check-traits.md) — why wiring is lazy and how checks force a readable error at the wiring site. -- [`check_components!`](../reference/macros/check_components.md) — the full checking surface, including `#[check_providers]` and checking generic components with concrete parameters. -- [`IsProviderFor`](../reference/traits/is_provider_for.md) and [`DelegateComponent`](../reference/traits/delegate_component.md) — the two traits every wiring error is ultimately about. -- [`#[cgp_namespace]`](../reference/macros/cgp_namespace.md) and [`DefaultNamespace`](../reference/traits/default_namespace.md) — the namespace-specific conflicts and orphan restrictions the decoder above references. diff --git a/docs/guides/declaring-dependencies.md b/docs/guides/declaring-dependencies.md deleted file mode 100644 index 3c1fb76a..00000000 --- a/docs/guides/declaring-dependencies.md +++ /dev/null @@ -1,42 +0,0 @@ -# Declaring a provider's dependencies - -A provider states what it needs from its context in its `where` clause, and this guide is about writing those needs as attributes that read like imports rather than as hand-written trait bounds. - -This guide follows on from [writing the provider header](writing-providers.md), which leaves the bounds off the header for these attributes to supply. - -## Declare dependencies with `#[uses]` and `#[use_provider]` - -State a provider's impl-side dependencies with [`#[uses(...)]`](../reference/attributes/uses.md) and [`#[use_provider(...)]`](../reference/attributes/use_provider.md) rather than hand-written `where` clauses, so a dependency reads like a `use` import instead of a trait bound. A capability the body calls on the context is imported with `#[uses]`: writing `#[uses(CanCalculateArea)]` adds `Self: CanCalculateArea` to the generated impl. An inner provider a [higher-order provider](../concepts/higher-order-providers.md) delegates to is declared with `#[use_provider]`: writing `#[use_provider(InnerCalculator: AreaCalculator)]` adds the bound `InnerCalculator: AreaCalculator`, filling in the `` argument that a provider trait inserts. The legacy `where` forms: - -```rust -#[cgp_impl(new ScaledArea)] -impl AreaCalculator for Context -where - Self: HasField, - InnerCalculator: AreaCalculator, -{ - fn area(&self) -> f64 { /* ... */ } -} -``` - -become: - -```rust -#[cgp_impl(new ScaledArea)] -#[use_provider(InnerCalculator: AreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] scale_factor: f64) -> f64 { /* ... */ } -} -``` - -Both attributes desugar to the same `where` predicates they replace. When a provider imports several capabilities or binds several inner providers, list them all in one attribute separated by commas — `#[uses(CanTransferMoney, CanRaiseHttpError)]`, `#[use_provider(A: TraitA, B: TraitB)]` — rather than stacking the same attribute repeatedly; one combined attribute reads as a single dependency list. - -`#[uses(...)]` accepts any bound a `where` clause allows, including one with associated-type equality, though the simple `Trait` form is the idiomatic one. One case moves elsewhere: when a bound pins an *abstract type* — `Self: HasErrorType` — express it with the [`#[use_type]` equality form](importing-abstract-types.md) rather than spelling the equality in `#[uses]` or a hand-written `where`. Only equality on a trait that is not a `#[use_type]` import (`Iterator`) stays an explicit `where` clause, where it reads more clearly than crammed into an import-shaped attribute. - -When a `#[uses]`-imported trait carries an abstract-type component as a *supertrait*, its associated type reaches the definition transitively — but prefer to also import that type explicitly with [`#[use_type]`](importing-abstract-types.md) rather than lean on the transitive `Self::Assoc`. If `CanCreateFoo` has `HasFooType` as a supertrait, write `#[uses(CanCreateFoo)]` *and* `#[use_type(HasFooType.Foo)]` so the signature names the bare `Foo`, with `#[uses]` declaring the capability dependency and `#[use_type]` declaring the type dependency — both visible, rather than the type riding in silently on the capability. See [importing abstract types](importing-abstract-types.md#re-import-a-type-that-arrives-through-a-supertrait). - -## Related guides - -- [Writing providers](writing-providers.md) — the `#[cgp_impl]` header these attributes attach to. -- [Importing abstract types](importing-abstract-types.md) — where an abstract-type pin belongs instead of `#[uses]`. -- [Guides summary](README.md#summary) — the cheat-sheet across all the guides, and the list of cases where an explicit `where` clause is still right. diff --git a/docs/guides/dispatching-per-type.md b/docs/guides/dispatching-per-type.md deleted file mode 100644 index 61fb02ab..00000000 --- a/docs/guides/dispatching-per-type.md +++ /dev/null @@ -1,43 +0,0 @@ -# Dispatching a component per type - -A component that is generic over a type parameter often wants a different provider per value of that parameter, and this guide is about doing that with the `open` statement or a namespace rather than the legacy `UseDelegate` nested table. - -This guide connects directly to [organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md), which develops the namespace side of the choice in depth. - -## Dispatch per type with `open` and namespaces, not `UseDelegate` - -Route a generic-parameter component to a different provider per type with the [`open` statement](../reference/macros/delegate_components.md) or a [namespace](namespaces-and-prefixes.md), rather than the legacy [`UseDelegate`](../reference/providers/use_delegate.md) nested-table pattern. Both the `open` statement and namespaces dispatch through the `RedirectLookup` impl that every [`#[cgp_component]`](../reference/macros/cgp_component.md) already generates, so they store the per-type entries directly on the context and need no wrapper type. The legacy form nests a `UseDelegate` table: - -```rust -delegate_components! { - MyApp { - AreaCalculatorComponent: - UseDelegate, - } -} -``` - -while dispatching inline with `open`: - -```rust -delegate_components! { - MyApp { - open AreaCalculatorComponent; - - @AreaCalculatorComponent.Rectangle: RectangleArea, - @AreaCalculatorComponent.Circle: CircleArea, - } -} -``` - -Because `open` and namespaces ride `RedirectLookup`, **a new component you intend to dispatch this way does not need the [`#[derive_delegate(UseDelegate)]`](../reference/attributes/derive_delegate.md) attribute at all** — that attribute exists only to generate the `UseDelegate` provider the legacy nested-table form relies on. You will still see `#[derive_delegate]` on some CGP-shipped components, such as the error and handler families, which carry it so existing `UseDelegate`-based wiring keeps working; but code that dispatches only through `open` or a namespace can omit it. - -Choose between `open` and a namespace by scope. Prefer `open` for a self-contained context wiring its own components directly — it folds the per-type entries into the context's own table with no separate type. Reach for a [namespace](namespaces-and-prefixes.md) when a reusable, inheritable dispatch table is worth sharing across contexts, or when a single generic component is served by several providers whose per-type entries you want to merge into one flat table. - -## Related guides - -- [Organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md) — the full namespace treatment, including flattening multi-provider dispatch that `open` alone cannot. -- [Guides summary](README.md#summary) — the cheat-sheet across all the guides. diff --git a/docs/guides/importing-abstract-types.md b/docs/guides/importing-abstract-types.md deleted file mode 100644 index 2dc99d8b..00000000 --- a/docs/guides/importing-abstract-types.md +++ /dev/null @@ -1,93 +0,0 @@ -# Importing abstract types - -CGP abstracts over types with associated types on components, and this guide is about bringing such a type into a definition as a plain alias rather than a supertrait plus a fully-qualified `Self::Type` at every use. - -It applies inside [`#[cgp_component]`](../reference/macros/cgp_component.md) definitions and [`#[cgp_impl]`](../reference/macros/cgp_impl.md)/[`#[cgp_fn]`](../reference/macros/cgp_fn.md) providers alike, and is the recommended form for the built-in error type as much as for a domain type. - -## Import abstract types with `#[use_type]` - -Bring an abstract type into a definition with [`#[use_type]`](../reference/attributes/use_type.md) and write it as a bare alias, rather than declaring the owning trait as a supertrait and qualifying every use as `Self::Type`. The attribute does both jobs at once: `#[use_type(HasScalarType.Scalar)]` adds the trait as a supertrait (on a `#[cgp_component]`) or a `where` bound (on a `#[cgp_impl]`/`#[cgp_fn]`), and rewrites each bare `Scalar` to `::Scalar`. This is the preferred form even for the built-in error type: the legacy component definition - -```rust -#[cgp_component(Loader)] -pub trait CanLoad: HasErrorType { - fn load(&self, path: &str) -> Result; -} -``` - -becomes - -```rust -#[cgp_component(Loader)] -#[use_type(HasErrorType.Error)] -pub trait CanLoad { - fn load(&self, path: &str) -> Result; -} -``` - -One rule bounds the rewrite: it fires only on the bare identifier of an *imported* type. A construct's own **local associated type always stays qualified as `Self::Assoc`** — a handler that declares `type Output` writes `Self::Output`, never a bare `Output`, because `Output` is the trait's own type rather than one imported from another trait. A mixed signature such as `Result` is therefore exactly right: the local `Self::Output` stays qualified while the imported foreign `Error` is written bare. - -When a definition imports types from several traits, combine them into one `#[use_type]` attribute by separating the trait paths with commas — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — rather than stacking one attribute per trait; the combined form reads as a single import list. Several types from one trait use a braced list (`#[use_type(HasFooType.{Foo, Bar})]`). - -## Import a foreign abstract type with `in Context` - -Prefer `#[use_type]` even when the abstract type lives on *another* type rather than on `Self` — a type named by a generic parameter. Add a trailing `in Context` clause: it rewrites the bare alias to `::Assoc` and adds `Context: Trait` as a bound, so you write neither the bound nor the qualified path by hand. This is the recommended form for a getter or method that reads a type off a parameter. The verbose form - -```rust -#[cgp_auto_getter] -pub trait HasLoggedInUser -where - App: HasUserIdType, -{ - fn logged_in_user(&self) -> &Option; -} -``` - -becomes - -```rust -#[cgp_auto_getter] -#[use_type(HasUserIdType.UserId in App)] -pub trait HasLoggedInUser { - fn logged_in_user(&self) -> &Option; -} -``` - -The `in App` clause supplies `App: HasUserIdType` on the generated trait, so the plain unbounded `` parameter is enough, and the signature names the bare `UserId` instead of `App::UserId`. The same clause works on `#[cgp_fn]` and `#[cgp_impl]`, and it composes: an `in Context` may itself point at another imported alias to chain through several hops. The written order of such chained imports does not matter — only a cycle, where two contexts resolve through each other, has no valid order and is rejected by the compiler. - -## Pinning an abstract type to a concrete one - -On a `#[cgp_impl]` or `#[cgp_fn]`, `#[use_type]` also *pins* an abstract type to a concrete one with the equality form `{Assoc = Type}`, which is the replacement for a hand-written `where Self: HasXType` clause. Writing `#[use_type(HasErrorType.{Error = AppError})]` emits `Self: HasErrorType` (and rewrites any bare `Error`), so a provider fixed to a concrete error type moves that pin out of its `where` clause and into the import. The right-hand side may name another imported alias to *unify* two abstract types — `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`. The equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side constraint it produces — this is the one place a pin stays in a hand-written `where` clause, and only for equality on a trait you would never `#[use_type]` from. - -## Re-import a type that arrives through a supertrait - -Import an abstract type with `#[use_type]` even when it *already* reaches the definition transitively — as the supertrait of a trait you pulled in with [`#[uses]`](declaring-dependencies.md). Relying on the transitive path forces the qualified `Self::Assoc`, which only resolves when the supertrait is reachable and names the type unambiguously; a second `#[use_type]` gives you the bare alias directly and states the dependency where a reader can see it. When `CanCreateFoo` carries `HasFooType` as a supertrait, prefer - -```rust -#[cgp_fn] -#[uses(CanCreateFoo)] -#[use_type(HasFooType.Foo)] -fn bar(&self) -> Foo { - self.create_foo() -} -``` - -over leaning on the transitive supertrait and writing the qualified path: - -```rust -#[cgp_fn] -#[uses(CanCreateFoo)] -fn bar(&self) -> Self::Foo { - self.create_foo() -} -``` - -The extra `#[use_type(HasFooType.Foo)]` re-adds `Self: HasFooType` — harmless, since it is already implied — and rewrites the bare `Foo` throughout, so the signature and body read the same way they would if the type were imported directly. `#[uses]` declares the *capability* dependency and `#[use_type]` declares the *type* dependency; naming both is clearer than making the type ride in silently on the other. - -When a capability supertrait has no associated type to import — a plain capability like `HasName` — add it with [`#[extend]`](capability-supertraits.md) rather than `#[use_type]`. Use `#[use_type]` when the signature names the trait's associated type; use `#[extend]` when it only calls the trait's methods. - -## Related guides - -- [Capability supertraits](capability-supertraits.md) — the companion for a supertrait that contributes a capability rather than a type. -- [Declaring dependencies](declaring-dependencies.md) — where an abstract-type pin moves *from* (a `#[uses]` or hand-written `where`). -- [Guides summary](README.md#summary) — the cheat-sheet across all the guides, with the local-associated-type exception restated among the other still-explicit cases. diff --git a/docs/guides/namespaces-and-prefixes.md b/docs/guides/namespaces-and-prefixes.md deleted file mode 100644 index 19b1b048..00000000 --- a/docs/guides/namespaces-and-prefixes.md +++ /dev/null @@ -1,314 +0,0 @@ -# Organizing wiring with namespaces and prefixes - -A context's [`delegate_components!`](../reference/macros/delegate_components.md) table grows one entry at a time until it is the hardest thing in the codebase to read; this guide shows how to shrink it back down with path prefixes, namespaces, and per-type defaults, worked as a refactoring of a real application. - -## The problem: a wiring table that outgrows its reader - -The first thing a newcomer sees when they open an application's context module is its wiring table, and by the time the application does anything interesting that table is long enough to overwhelm them. Each component the application uses adds a line, each abstract type adds a line, and each per-type dispatch adds several. The table is mechanically correct and every entry is doing real work, but there is no structure to hold onto — it reads as one flat list of thirty unrelated facts, and a reader cannot tell at a glance which entries belong together or which are the ones they came to change. - -Consider a small money-transfer web service. It has an authentication layer, a finance layer, an HTTP error-mapping layer, and an API layer, wired onto a single `MockApp` context whose backend is an in-memory mock. Written out entry by entry, its table looks like this: - -```rust -delegate_components! { - MockApp { - open { - HttpErrorRaiserComponent, - ApiHandlerComponent, - }; - - ErrorTypeProviderComponent: UseType, - - @HttpErrorRaiserComponent. Code.String: - DisplayHttpError, - @HttpErrorRaiserComponent. Code.anyhow::Error: - HandleHttpErrorWithAnyhow, - - [ - UserIdTypeProviderComponent, - PasswordTypeProviderComponent, - HashedPasswordTypeProviderComponent, - ]: - UseType, - QuantityTypeProviderComponent: - UseType, - CurrencyTypeProviderComponent: - UseType, - - [ - PasswordCheckerComponent, - UserHashedPasswordQuerierComponent, - UserBalanceQuerierComponent, - ]: - UseMockedApp, - MoneyTransferrerComponent: - NoTransferToSelf, - - @ApiHandlerComponent.QueryBalanceApi: - HandleFromRequest< - AxumQueryBalanceRequest, - ResponseToJson>>, - >, - @ApiHandlerComponent.TransferApi: - HandleFromRequest< - AxumTransferRequest, - UseBasicAuth>, - >, - } -} -``` - -Every line here is necessary, and nothing about it is wrong. The problem is purely one of presentation: the table mixes error handling, abstract types, business logic, and API routing with no visible seam between them, and a second context that shared most of this wiring would have to copy the whole block. The rest of this guide refactors this exact table down to a handful of lines, introducing one technique at a time. The three techniques compose, and each is useful on its own, so you can stop at whichever level of organization your application needs. - -## Technique 1: group components under path prefixes with `#[prefix]` - -The first move is to give each component a **path** — a dotted address like `@app.auth` or `@app.finance` — so that related components sort together instead of scattering through the table. The [`#[prefix(@path in Namespace)]`](../reference/macros/cgp_namespace.md) attribute on a component's [`#[cgp_component]`](../reference/macros/cgp_component.md) (or [`#[cgp_type]`](../reference/macros/cgp_type.md)) trait registers that component into a namespace under a path prefix, so that from then on the component is addressed by its path rather than by its bare marker name. The standard namespace to register into is the built-in [`DefaultNamespace`](../reference/traits/default_namespace.md), which every context can join. - -The abstract types divide cleanly by the layer that owns them, so they take a `types` sub-path under each layer: - -```rust -#[cgp_type] -#[prefix(@app.auth.types in DefaultNamespace)] -pub trait HasUserIdType { - type UserId: Display; -} - -#[cgp_type] -#[prefix(@app.finance.types in DefaultNamespace)] -pub trait HasQuantityType { - type Quantity: Display; -} -``` - -The capability components take the layer path directly, without the `types` segment, so the authentication logic sits under `@app.auth` and the finance logic under `@app.finance`: - -```rust -#[cgp_component(PasswordChecker)] -#[prefix(@app.auth in DefaultNamespace)] -#[use_type(HasPasswordType.Password, HasHashedPasswordType.HashedPassword)] -pub trait CanCheckPassword { - fn check_password(password: &Password, hashed_password: &HashedPassword) -> bool; -} - -#[cgp_component(UserBalanceQuerier)] -#[prefix(@app.finance in DefaultNamespace)] -#[async_trait] -#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity, HasErrorType.Error)] -pub trait CanQueryUserBalance { - async fn query_user_balance(&self, user: &UserId, currency: &Currency) -> Result; -} -``` - -The HTTP error raiser and the API handler each get their own layer path, `@app.error` and `@app.api`. With every component prefixed, the application's whole namespace looks like a directory tree — `@app.auth.types`, `@app.auth`, `@app.finance.types`, `@app.finance`, `@app.error`, `@app.api` — and a reader can find the auth wiring without reading the finance wiring. - -### Choosing prefixes for implementations you have not written yet - -The prefix you choose depends less on how *this* application wires a component and more on whether a *different* implementation would ever wire it separately. The natural instinct is to group by the provider you happen to use: since one `UseType` serves all three auth types in this application, you might put them wherever is convenient. Resist collapsing distinctions that a future implementation would need. A component author picks prefixes for every implementation the component might ever have, not just the one in front of them, because the prefix is part of the component's public surface and is expensive to change once downstream code depends on it. - -The rule that follows is to **give components a separate sub-path whenever they are likely to need separate providers**, even when the current implementation happens to wire them the same way. The abstract types sit under `@app.auth.types` rather than sharing `@app.auth` with the logic, because a real backend would supply the auth *logic* (password checking, hashed-password lookup) very differently from how it supplies the auth *types* — the types are almost always plain `UseType` while the logic talks to a database. Keeping them on separate sub-paths means a production context can point `@app.auth` at a database provider while leaving `@app.auth.types` on the same concrete types, without either wiring disturbing the other. Grouping them together would have read fine today and forced them apart tomorrow. - -## Technique 2: bind providers to a namespace so the context just joins it - -Prefixes organize the table but do not shorten it — the context still names every path. The second technique lifts the wiring off the context entirely and into a reusable **namespace**, so that most contexts join the namespace with a single line and wire nothing directly. A namespace defined with [`cgp_namespace!`](../reference/macros/cgp_namespace.md) is a preset: a named table of default wirings a context inherits wholesale and then selectively overrides. (The [namespaces concept](../concepts/namespaces.md) explains the mechanism; this section is about how to *use* it to organize an application.) - -Define one namespace for the application's mock backend, inheriting `DefaultNamespace` so a context that joins it also inherits every standard default: - -```rust -cgp_namespace! { - new MockNamespace: DefaultNamespace { - @cgp.core.error.ErrorTypeProviderComponent: - UseType, - - @app.error.HttpErrorRaiserComponent. Code.String: - DisplayHttpError, - @app.error.HttpErrorRaiserComponent. Code.anyhow::Error: - HandleHttpErrorWithAnyhow, - - @app.auth.types.{ - UserIdTypeProviderComponent, - PasswordTypeProviderComponent, - HashedPasswordTypeProviderComponent, - }: - UseType, - @app.finance.types.QuantityTypeProviderComponent: - UseType, - @app.finance.types.CurrencyTypeProviderComponent: - UseType, - } -} -``` - -These entries are the wirings that have no [`#[cgp_impl]`](../reference/macros/cgp_impl.md) block of their own to attach an attribute to: the concrete error type, the HTTP error dispatch, and the abstract-type choices are all built from library providers like [`UseType`](../reference/providers/use_type.md), so they are written directly in the namespace **body**, keyed by their full paths. The body of a namespace accepts exactly the same key and value forms as `delegate_components!`, including grouped keys (`@app.auth.types.{A, B, C}`) and generic-parameter dispatch keys (`@app.error.HttpErrorRaiserComponent. Code.String`). - -### Registering a provider with `#[default_impl]` - -The application's business logic *does* have `#[cgp_impl]` blocks — the mock backend implements password checking, hashed-password lookup, and balance querying — so those providers register themselves into the namespace from their own definition, with the [`#[default_impl(@path in Namespace)]`](../reference/traits/default_namespace.md) attribute: - -```rust -#[cgp_impl(UseMockedApp)] -#[default_impl(@app.auth.UserHashedPasswordQuerierComponent in MockNamespace)] -#[use_type(HasUserIdType.UserId, HasHashedPasswordType.HashedPassword, HasErrorType.Error)] -impl UserHashedPasswordQuerier -where - UserId: Ord, - HashedPassword: Clone, -{ - async fn query_user_hashed_password( - &self, - user_id: &UserId, - #[implicit] user_passwords: &BTreeMap, - ) -> Result, Error> { - Ok(user_passwords.get(user_id).cloned()) - } -} -``` - -`#[default_impl]` emits one extra impl that maps the path `@app.auth.UserHashedPasswordQuerierComponent` to `UseMockedApp` inside `MockNamespace`, without changing the provider itself. The effect is that the wiring lives next to the implementation it wires, which is where a reader looks for it, rather than in a distant table. A context that joins `MockNamespace` now resolves `UserHashedPasswordQuerier` to `UseMockedApp` automatically. - -With the namespace carrying the whole backend, a context that wants the mock backend joins it with one statement: - -```rust -delegate_components! { - MockApp { - namespace MockNamespace; - } -} -``` - -Everything the earlier table spelled out by hand — the error type, the error dispatch, the abstract types, the auth and finance logic — now arrives through the namespace, and a second mock context would need only the same single line. - -### Keep `#[prefix]` and `#[default_impl]` in different namespaces - -A subtle but important rule governs which namespace each attribute names: **register a component's `#[prefix]` into a base namespace and its `#[default_impl]` into a namespace that inherits the base, never the same one.** In the example, every `#[prefix]` names `DefaultNamespace` while every `#[default_impl]` names `MockNamespace`, and `MockNamespace: DefaultNamespace` inherits the prefixes. This separation is not stylistic. A namespace's entry for a key, once defined, cannot be overridden — so if the prefixes and the mock defaults shared one namespace, a *second* backend (a production one, say) could not reuse the prefixes without also inheriting the mock's providers, and would have to redeclare every prefix from scratch. Splitting them lets the prefix layer be reused by any number of backend namespaces, each supplying its own `#[default_impl]` bindings on top of the shared prefixes. Put the prefixes in the base namespace that describes the application's *structure*, and put each backend's provider choices in an inheriting namespace that describes one *configuration*. - -## Technique 3: merge several namespaces in one table with a `for` loop - -The API handlers are the one part of the wiring that is neither a plain library provider nor a `#[cgp_impl]` block — each is a hand-assembled pipeline of combinators — and they describe the application's public API surface rather than one backend's choices. That makes them a good fit for a *separate* namespace that any backend can pull in. Define the API surface as its own table keyed by the API marker: - -```rust -cgp_namespace! { - new DefaultApiHandlers { - QueryBalanceApi: - HandleFromRequest< - AxumQueryBalanceRequest, - ResponseToJson>>, - >, - TransferApi: - HandleFromRequest< - AxumTransferRequest, - UseBasicAuth>, - >, - } -} -``` - -A context then pulls this table onto its own `ApiHandler` dispatch path with a `for` loop, which reads each entry of the named table and emits one mapping per entry: - -```rust -delegate_components! { - MockApp { - namespace MockNamespace; - - for in DefaultApiHandlers { - @app.api.ApiHandlerComponent.Key: Value, - } - } -} -``` - -The `for in DefaultApiHandlers` loop binds each `QueryBalanceApi`/`TransferApi` entry as `Key` and its pipeline as `Value`, wiring `MockApp`'s `ApiHandler` for that API to that pipeline. This is how a single table draws on **more than one** namespace at once: `MockApp` joins `MockNamespace` for its backend *and* loops over `DefaultApiHandlers` for its API surface, keeping the two concerns in separate reusable tables while merging them onto one context. - -One constraint shapes how the loop key is written: **the loop's bound key must appear inside a path, never as the whole key.** Writing `Key: Value` on its own would collide with the general `DelegateComponent` impl that the `namespace` statement already generates for every key; embedding it in a path as `@app.api.ApiHandlerComponent.Key` keeps it distinct. This is also why the loop is the natural tool for a component with a generic parameter — the API marker *is* the dispatch parameter of `ApiHandlerComponent`, so each looped entry lands on that component's per-type dispatch path. - -## The payoff, and overriding through a namespace - -The three techniques together turn the thirty-line opening table into four lines, and the difference is not just length — it is that each remaining line now states a *decision* rather than a mechanical fact: - -```rust -delegate_components! { - MockApp { - namespace MockNamespace; - - for in DefaultApiHandlers { - @app.api.ApiHandlerComponent.Key: Value, - } - - @app.finance.MoneyTransferrerComponent: - NoTransferToSelf, - } -} -``` - -`MockApp` uses the mock backend, serves the default API surface, and — the one place it departs from the defaults — wraps money transfers in a `NoTransferToSelf` guard that rejects a transfer whose sender and recipient are the same account. That last entry is an **override**, and overriding through a namespace follows a rule worth stating outright: **a context can only wire a path that the namespace it joins does not itself register.** `MockNamespace` deliberately does *not* register `@app.finance.MoneyTransferrerComponent` — the base `MoneyTransferrer` provider is used only as the inner handler of the `NoTransferToSelf` wrapper, never registered on its own — so the context is free to wire that path directly. Had the namespace registered a provider at that exact path, the context's entry and the namespace's blanket forwarding would both implement `DelegateComponent` for that key, and the compiler would reject the overlap. To leave a path open for a context to override, route the component through the namespace but terminate the redirect on the context, not in the namespace. - -## Limitations: why `#[default_impl]` is for the basic case - -`#[default_impl]` is the most convenient of these tools and also the most constrained, and knowing where it stops keeps you from designing around it and then hitting a wall. Its central limitation is that **a `#[default_impl]` must live in the same crate as the namespace it registers into, whenever the component carries a prefix.** The attribute expands to `impl Namespace<_> for PathCons<..>`, and for a prefixed component that path is built entirely from the `cgp`-owned `PathCons`/`Symbol` types and the component's marker; Rust's orphan rule then accepts the impl only if the crate owns the `Namespace` trait. A downstream crate cannot register a default for an upstream prefixed component into an upstream namespace — the whole impl is foreign to it. - -The restriction relaxes only when no prefix is involved. For a component without a prefix, `#[default_impl(Component in Namespace)]` expands to `impl Namespace<_> for Component`, whose key is the component's own marker; a downstream crate that owns that marker can register it into a foreign namespace, because the marker is a local type. So a crate may register a default when it owns *either* the namespace or the un-prefixed component key — but a prefixed component's key is a foreign path, leaving only the "owns the namespace" option. - -Two consequences follow for how you reach for the tool. First, `#[default_impl]` couples an implementation to the namespace's crate, so splitting an application into finer crates eventually forces the provider, the component, and the namespace together in ways that reduce modularity — the opposite of what CGP's crate split is for. Second, because a namespace entry cannot be overridden once set, a `#[default_impl]` bakes in a choice that inheriting namespaces cannot revise. For these reasons **`#[default_impl]` is best seen as the tool for the basic case** — an application still written in a single crate, as a newcomer naturally starts — where it earns its keep by letting them add CGP wiring gradually, keeping their impls looking almost exactly like ordinary trait impls, and deferring the full wiring tables until later. When an application outgrows a single crate, move the affected wirings from `#[default_impl]` attributes into namespace **body** entries, which have no such crate restriction because the namespace's own crate writes them. - -## Advanced: flattening multi-provider dispatch into one table - -The deepest payoff of namespace paths appears when a single generic-parameter component is served by *several* providers depending on its parameter, and the providers live in different crates. The traditional way to dispatch such a component is a nested [`UseDelegate`](../reference/providers/use_delegate.md) table per provider, which forces the wiring into a tree of inner tables. Namespace paths let you flatten that tree into one table, because every entry is addressed by a full path and so entries for the same component but different providers can sit side by side. - -A handler component dispatched on a code parameter shows the shape. Rather than one `UseDelegate` table wrapping all handlers, each group of codes is mapped to its provider by a path with the code inline, and the groups for different providers coexist in one namespace: - -```rust -cgp_namespace! { - new AppNamespace: DefaultNamespace { - @cgp.extra.handler.HandlerComponent.[ - BytesToString, - ConvertTo, - Pipe, - ]: - BaseHandlerProvider, - - @cgp.extra.handler.HandlerComponent.[ - ReadFile, - StreamToBytes, - ]: - FileHandlerProvider, - - @cgp.extra.handler.HandlerComponent.[ - HttpRequest, - ]: - HttpHandlerProvider, - } -} -``` - -Each `@cgp.extra.handler.HandlerComponent.[..]` block maps a set of code types — some carrying their own generic parameters, bound with the inline `` form — to one provider, and the three blocks for three providers read as one flat list of "which provider handles which codes." The equivalent `UseDelegate` wiring would nest three inner tables inside an outer dispatch, and merging a fourth provider's codes would mean editing the tree; here it is one more block. Flattening this way is the pattern to reach for once a component is dispatched across providers that live in separate crates, since each crate can contribute its block to a shared namespace by owning the block's provider, without any crate needing to own the others' entries. - -### Collapsing a whole subtree with nested groups - -When one provider serves *many* components spread across several sub-paths, the path grouping nests, so a single entry can map an entire subtree to one provider. A `.{ … }` group after a path segment holds a comma-separated list of continuations, each of which is itself a path that may carry its own `.[ … ]` code list or another `.{ … }` group. Mapping every component that a single reqwest-based provider handles — some under a `core` sub-path, some under a `reqwest` sub-path, several dispatched on their own code parameters — collapses to one entry: - -```rust -@app.{ - core.{ - HttpMethodTypeProviderComponent, - UrlTypeProviderComponent, - MethodArgExtractorComponent.[GetMethod, PostMethod], - }, - reqwest.RequestBuilderUpdaterComponent.[ - WithHeaders, - Header, - ], -}: - ReqwestProvider, -``` - -The `@app.{ core.{ … }, reqwest.… }` shape reads as a directory listing: everything under `core` and everything under `reqwest` named here resolves to `ReqwestProvider`, with the leaf `.[ … ]` lists dispatching the generic-parameter components on their codes. Written out flat, this is seven separate `@app.core.…` and `@app.reqwest.…` entries; nested, it is one. Reach for nesting when a provider owns a cohesive slice of the namespace — it keeps the "one provider, these components" relationship on a single entry instead of scattering it down the table. - -## Choosing how far to go - -The three techniques form a ladder, and most applications should climb only as far as they need. Reach for `#[prefix]` as soon as a table has enough components that grouping helps a reader — it costs nothing and pays off immediately in navigability, and it is the one technique with no downstream restriction. Add a namespace with `#[default_impl]` once two contexts would share most of their wiring, or once a newcomer wants their impls to carry their own wiring instead of a central table, accepting that this ties the wiring to one crate. Split wiring across several namespaces with `for` loops, and flatten multi-provider dispatch into paths, when the application is large enough that different concerns — a backend, an API surface, a set of handlers — genuinely deserve their own reusable tables. Stop at the rung that makes the wiring clear; the goal is a table a reader can hold in their head, not the maximum use of the machinery. - -## Related documentation - -- [`#[cgp_namespace]`](../reference/macros/cgp_namespace.md) — the full syntax of defining a namespace, the `namespace`/`for … in` statements, and the `#[prefix]` attribute. -- [`DefaultNamespace`, `DefaultImpls1`, `DefaultImpls2`](../reference/traits/default_namespace.md) — the lookup traits behind namespaces and the `#[default_impl]` attribute. -- [`delegate_components!`](../reference/macros/delegate_components.md) — the wiring table these techniques restructure, including the `open` statement for the self-contained case. -- [Namespaces](../concepts/namespaces.md) — the mechanism (inheritance, `RedirectLookup`, and paths) that makes the preset pattern work. -- [Guides summary](README.md#summary) — the condensed cheat-sheet for the provider, dependency, and abstract-type idioms the code above uses. diff --git a/docs/guides/reading-context-fields.md b/docs/guides/reading-context-fields.md deleted file mode 100644 index 1ea2d85c..00000000 --- a/docs/guides/reading-context-fields.md +++ /dev/null @@ -1,46 +0,0 @@ -# Reading context fields - -A provider reads values from its context's fields, and this guide is about doing that with an argument that looks like an ordinary parameter rather than a getter trait declared just to fetch it. - -This guide is the value-level counterpart to [writing providers](writing-providers.md) — the same move from visible machinery to an ordinary-looking parameter, applied to reading a field. - -## Read context fields with implicit arguments, not getter traits - -Read a value from a context field with an [`#[implicit]`](../reference/attributes/implicit.md) argument — in a [`#[cgp_impl]`](../reference/macros/cgp_impl.md) provider method just as in a [`#[cgp_fn]`](../reference/macros/cgp_fn.md) — rather than declaring a getter trait with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md). An implicit argument names both a local variable and the field it is read from, so the field access reads like an ordinary parameter and the `HasField` machinery stays out of sight. This is the default way to pull a field into a provider, and it covers the great majority of field reads: a value used throughout a body is bound once at the top, and a value shared across several methods is simply declared as an implicit argument on each. The getter-trait version pairs a `#[cgp_auto_getter]` declaration with a `#[uses(...)]` import: - -```rust -#[cgp_auto_getter] -pub trait HasDimensions { - fn width(&self) -> &f64; - fn height(&self) -> &f64; -} - -#[cgp_impl(new RectangleArea)] -#[uses(HasDimensions)] -impl AreaCalculator { - fn area(&self) -> f64 { - self.width() * self.height() - } -} -``` - -collapses to a provider that reads the two fields directly: - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} -``` - -Use `#[cgp_auto_getter]` sparingly — only where an implicit argument cannot reach the field. Because an implicit argument reads from the provider's own `self` and takes a plain `&T` by reference without cloning, it covers every same-context read, including a field several providers each consume. A getter trait earns its keep in the three cases an implicit argument cannot serve: a field that lives on a type *other* than the provider's context, so the getter is required as a `where` bound on that type (`Request: HasBasicAuthHeader`, with no `self` field to read); an accessor that must exist as a *named* capability other code depends on through `#[uses(HasName)]` or a supertrait; and a getter whose associated type is inferred from the field (`type Name; fn name(&self) -> &Self::Name;`) so the type stays abstract for callers. Both idioms desugar to the same `HasField` bounds and share the same access rules — `.clone()` for an owned value, `.as_str()` for a `&str`, a plain `&T` by reference — so the choice is only about whether an implicit argument can reach the value. - -Avoid [`#[cgp_getter]`](../reference/macros/cgp_getter.md) in ordinary code. It builds a full wireable component so the source field name can be chosen at wiring time through a [`UseField`](../reference/providers/use_field.md) provider, and that flexibility is reserved for the advanced case where you want full control over the context implementation — deciding per context which field a getter reads from, or supplying the value by means other than a same-named field. For the common case of reading a field, an implicit argument is the form to write, with `#[cgp_auto_getter]` held back for the getter-only cases above. - -## Related guides - -- [Writing providers](writing-providers.md) — the `#[cgp_impl]` provider these arguments live in. -- [Importing abstract types](importing-abstract-types.md) — for a getter whose return type is an abstract type shared across contexts. -- [Guides summary](README.md#summary) — the cheat-sheet across all the guides, and the cases where a getter trait is still the right tool. diff --git a/docs/guides/writing-providers.md b/docs/guides/writing-providers.md deleted file mode 100644 index be3b46f9..00000000 --- a/docs/guides/writing-providers.md +++ /dev/null @@ -1,70 +0,0 @@ -# Writing providers - -A provider can be written at three levels of sugar over the same machinery, and this guide is about choosing the highest one — writing a provider that reads like an ordinary trait `impl` rather than the inside-out provider-trait form the macros desugar to. - -This guide pairs with [declaring a provider's dependencies](declaring-dependencies.md) and [reading its context's fields](reading-context-fields.md), which cover what goes inside the provider once its header is written this way. - -## Write providers with `#[cgp_impl]`, not the raw provider forms - -Write a provider with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), which keeps `self`, `Self`, and the consumer method signatures, rather than with the lower-level [`#[cgp_provider]`](../reference/macros/cgp_provider.md) or [`#[cgp_new_provider]`](../reference/macros/cgp_new_provider.md), which require the inside-out provider-trait shape. The lower forms move the context into an explicit leading type parameter and force the method to take `context: &Context` instead of `&self`; `#[cgp_impl]` restores the familiar shape and performs that rewrite for you. The legacy form: - -```rust -#[cgp_new_provider] -impl AreaCalculator for RectangleArea -where - Context: HasField, - Context: HasField, -{ - fn area(context: &Context) -> f64 { - *context.get_field(PhantomData::) - * *context.get_field(PhantomData::) - } -} -``` - -becomes, with [`#[implicit]`](../reference/attributes/implicit.md) arguments: - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} -``` - -`#[cgp_impl]` desugars back to `#[cgp_provider]`/`#[cgp_new_provider]`, so the raw forms are still what the reference documents show in their Expansion sections and what you read in generated code. Write the raw form yourself only when you specifically need the inside-out shape — for instance, to state a bound the sugar cannot express, or to implement a provider trait on a concrete context rather than a generic one. - -## Omit the context parameter - -Inside a `#[cgp_impl]` block, prefer the unqualified `impl AreaCalculator` and let the macro insert the context parameter, rather than naming it explicitly as `impl AreaCalculator for Context`. Omitting `for Context` is what makes the provider read like an ordinary trait `impl`; the macro supplies a reserved context parameter and treats `self`/`Self` as the context. Write the context out by hand: - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator for Context -where - Context: HasDimensions, -{ - fn area(&self) -> f64 { - self.width() * self.height() - } -} -``` - -only when you must name it — to bound it with a lifetime or higher-ranked bound the sugar cannot spell, or to refer to it by a readable name. Otherwise write the shorter form and declare the bound with [`#[uses(...)]`](declaring-dependencies.md): - -```rust -#[cgp_impl(new RectangleArea)] -#[uses(HasDimensions)] -impl AreaCalculator { - fn area(&self) -> f64 { - self.width() * self.height() - } -} -``` - -## Related guides - -- [Declaring a provider's dependencies](declaring-dependencies.md) — state the `where` bounds this idiom leaves off the header with `#[uses]` and `#[use_provider]`. -- [Reading context fields](reading-context-fields.md) — pull field values into a provider with `#[implicit]` arguments, as the first example does. -- [Guides summary](README.md#summary) — the cheat-sheet across all the guides, and the list of when an explicit form is still right. diff --git a/docs/implementation/AGENTS.md b/docs/implementation/AGENTS.md deleted file mode 100644 index b6934687..00000000 --- a/docs/implementation/AGENTS.md +++ /dev/null @@ -1,78 +0,0 @@ -# AGENTS.md — the CGP implementation documentation - -This directory documents the *internals* of the CGP macros — how each macro is implemented, not how a programmer uses it. Read [README.md](README.md) for the catalog and the map of what lives where. The rules below govern how to keep these documents correct and useful, and they assume you have already read the knowledge-base-wide rules in [../AGENTS.md](../AGENTS.md); this file adds the rules specific to the implementation tree. - -Invoke the `/cgp` skill before writing or revising any document here, exactly as the reference documents require, and write in the dual-reader prose style (the `/dual-reader-prose` skill). An implementation document is read both by an agent scanning for one detail of a parser and by an agent reading a macro's pipeline start to finish, so every section opens with a self-contained topic sentence and frames any list with a sentence before and after. - -## What these documents are for - -The implementation documents are the working notes an agent needs to review, debug, or extend the CGP macro source, written so that an agent can pick up a construct's implementation from exactly where the last one left off. Where a [reference document](../reference/README.md) explains what a construct *does* for a user — its accepted syntax and the code it expands to — an implementation document explains how the macro *produces* that behavior: the AST types it parses into, the pipeline stages it moves through, the functions that synthesize each generated item, the corner cases the code handles or mishandles, and the known limitations and bugs. When an agent is asked to fix a macro, this is the document it should open first, because it records the current state of the code in one place rather than making the agent reconstruct it from the source. - -These documents are the home for everything about the *test suite* that the reference documents deliberately exclude. A reference document points only at library source and never at a test; every pointer into [crates/tests](../../crates/tests) — behavioral tests, failure cases, and macro-expansion snapshots — lives here instead, in the Tests and Snapshots sections described below. This split keeps the reference focused on user-facing semantics while making the implementation documents the single index an agent consults to learn what is tested, what is pinned by a snapshot, and what coverage is still missing. - -## The synchronization rule applies here too - -An implementation document must stay in sync with the code, and keeping it in sync is part of the change, not a follow-up. The source in [crates/macros/cgp-macro-core](../../crates/macros/cgp-macro-core) and [crates/macros/cgp-macro-lib](../../crates/macros/cgp-macro-lib) is the single source of truth, above both this document and the reference. When you change a parser, a pipeline stage, the set of generated items, or an error path, revise the matching implementation document in the same change; when you add or remove a test or a snapshot, update the Tests or Snapshots section that indexes it. A document that describes a pipeline the code no longer has is worse than no document, because the next agent will trust it. Verify every claim against the source before writing it — read the `types//` modules, the `functions/` helpers, and the tests — rather than transcribing the reference or working from memory. - -Document the present, not the history, following [../AGENTS.md](../AGENTS.md): describe how the code works now, record current limitations under Known issues, and delete superseded wording outright rather than leaving "previously" or "renamed from" traces. - -Improving the code's inline docs is part of writing an implementation document, not a separate task. Reading a macro's source closely enough to document it is exactly when to fix its inline docs, so in the same pass add a brief `///` to any public struct, trait, or function that lacks one, correct a comment that no longer matches the code, clarify genuinely confusing code, and delete comments that only restate the obvious — the convention recorded in [cgp-macro-core/AGENTS.md](../../crates/macros/cgp-macro-core/AGENTS.md). Keep the inline docs terse and leave the deep mechanics to the implementation document; a one-line inline doc that links out to the document beats a paragraph inlined in the source. - -## Directory layout - -The tree is organized by *what kind of source construct* a document describes, so an agent looking for "the macro entry point", "the AST type behind a stage", "a shared helper function", or "an internal `macro_rules!`" each has an obvious place to start. Every document registers itself in [README.md](README.md) in the same change that creates it. - -The [entrypoints/](entrypoints/) directory holds one document per CGP macro — the top-level procedural macro a programmer invokes. Each names the `cgp-macro-lib` entry function, walks the transform pipeline it drives, enumerates the items the macro emits and the function that produces each, and records the macro's corner cases, known issues, tests, and snapshots. This is the document an agent maintaining a specific macro opens first. - -The [asts/](asts/) directory holds one document per *evaluation stack* of AST constructs — the types in `cgp-macro-core` that implement `syn::parse::Parse` or `quote::ToTokens`, or that serve as an intermediate representation between the two. Multiple AST types that belong to the same stack share one document: the `cgp_component` stack, for example, documents `ItemCgpComponent`, `PreprocessedCgpComponent`, and `EvaluatedCgpComponent` together, along with the argument types they parse from, because they are stages of one pipeline and are only understood as a sequence. - -The [functions/](functions/) directory holds the standalone helper functions that are not organized as AST types — the free functions in `cgp-macro-core/src/functions`. It is split into subdirectories by what the functions do: [functions/parse/](functions/parse/) groups the parsing helpers (functions that consume tokens or a `syn` node into a more specific form), and [functions/derive/](functions/derive/) groups the derivation helpers (functions that synthesize generated `syn` items from a parsed construct). Add a further subdirectory when a distinct family of helpers grows large enough — case conversion or generics merging, for instance — rather than letting one directory sprawl. - -The [macros/](macros/) directory holds the internal `macro_rules!` macros that the CGP macro implementation itself relies on — `parse_internal!`, `define_keyword!`, the `export_construct(s)!` family — as opposed to the user-facing procedural macros documented under `entrypoints/`. These are the building blocks the codegen is written in, and a document here explains what the macro expands to and how the rest of the implementation uses it. - -Because documents live in different subdirectories, a cross-link between two of them is a relative path — a sibling is `name.md`, and a document in another directory is `../that-dir/name.md`. Link generously between an entrypoint and the AST stack, functions, and internal macros it uses, so an agent can follow a macro from its entry function down into every helper it calls. - -## Document structure - -Each kind of document follows a shape so an agent can navigate any of them by habit. All kinds share the same opening — a level-one heading naming the construct and a one-sentence summary — and the same closing pair of a **Tests** section and a **Source** section. The middle sections differ by kind. - -An **entrypoint document** (`entrypoints/.md`) proceeds through: **Entry point** — the `cgp-macro-lib` function, what it parses the attribute and item into, and the pipeline call it drives; **Pipeline** — each stage in order (for `cgp_component`, `preprocess → eval → to_items`), what each stage does at a high level, and a link to the [asts/](asts/) document that owns those types; **Generated items** — what the macro emits and in what order, described at a high level and illustrated with a code snippet where a transform is worth showing; **Behavior and corner cases** — how the implementation handles supertraits, default methods, generic parameters, lifetimes, reserved identifiers, and any input the macro treats specially; **Failure modes** — optional; the ways the macro's *accepted* input can still fail to compile downstream that are *intended* (deferred to the compiler), following the rule below; **Known issues** — limitations and bugs, including the *problematic* compile-failure modes, following the rule below; then **Snapshots**, **Tests**, and **Source**. Omit the Failure modes heading when a macro has no acceptable compile-failure mode worth recording. - -An **AST document** (`asts/.md`) opens with a short overview of the stack as a pipeline, then gives one section per AST type in the order the data flows through them. Each section states the type's role, the shape it parses from or emits into (with a snippet where a grammar or intermediary form is worth showing), and how it hands off to the next stage, and links to the entrypoint document that drives the stack. It then closes with **Tests** and **Source**. - -A **function document** (`functions//.md`) opens with what the group of functions is for and describes what each public function does and its corner cases at a high level, closing with **Tests** and **Source**. A **macro document** (`macros/.md`) describes what the internal `macro_rules!` expands to, how the implementation is expected to call it (for `parse_internal!`, that it expands to a `?` expression and must be called in a `syn::Result`-returning function), and closes with **Tests** and **Source**. - -## Level of detail and code snippets - -An implementation document gives the high-level picture of how a construct works; the code holds the details, so do not rehash it line by line. Explain what the construct does and why, name the stages it moves through and the shape of what it produces, and leave the mechanics of each step to the source. Mention an internal function only when a reader needs it as an entry point into the code, or when the explanation must cross into another implementation document — do not walk a call chain naming every helper it touches. Break an explanation into ordered steps only when it jumps between documents (a macro whose expansion depends on a helper documented elsewhere, say); within a single document, prefer a high-level description over a step-by-step trace of the code. - -Use code snippets to make specific behavior concrete. When you describe how input is parsed or what code is generated, a short before/after snippet is usually clearer than prose — show the parsed form, an intermediary representation the user never writes, or the slice of generated output that illustrates the point. These snippets are deliberately not exhaustive: the reference document already shows the full accepted syntax and the complete expansion, so an implementation snippet covers only the fragment that explains the behavior at hand — one corner case, one intermediary form, or how one construct lowers into another. Prefer illustrating a tricky transform (a supertrait becoming a `where`-bound, a lifetime lifted into `Life<'a>`) with a small example over describing it in the abstract. - -When a section lists many items one at a time — the tests, the snapshots, a set of accepted keys — use a bullet list rather than framed prose. The dual-reader topic-sentence style governs the explanatory prose, not these enumerations: introduce the list with a short sentence and let the bullets carry the items. - -The **Tests** and **Source** sections are always bullet lists, never flowing paragraphs — one test (or snapshot) per bullet, and one source pointer per bullet. A short lead-in sentence before the bullets is allowed (and is the norm for Tests), but the items themselves must be bullets so a reader can scan a construct's coverage and its code locations at a glance. - -## The Tests and Snapshots sections - -Every document links its related tests in a **Tests** section, stating the behavior each test pins in the document's own words. List the behavioral tests in [crates/tests/cgp-tests](../../crates/tests/cgp-tests) and the rejection cases in [crates/tests/cgp-macro-tests](../../crates/tests/cgp-macro-tests) that exercise the construct, each as a link to the file with a one-line description of what it verifies. The post-codegen compile-fail cases no longer live in this repository: they are UI fixtures in `cargo-cgp` (linked by GitHub URL) and cataloged by class in the [error catalog](../errors/README.md), so a `## Failure modes` section links out to the catalog class and its backing `cargo-cgp` fixture rather than to a now-removed local fixture. Because the reference documents no longer point at tests, this section is the canonical index of a construct's coverage — write it so an agent can see at a glance what behavior is guarded and, by omission, what is not. - -Every entrypoint document additionally carries a **Snapshots** section that indexes the macro-expansion snapshots for that macro and calls out which variants are missing. Macro snapshots are pinned by the `snapshot_*!` macros in [crates/macros/cgp-macro-test-util](../../crates/macros/cgp-macro-test-util) and, per [crates/tests/AGENTS.md](../../crates/tests/AGENTS.md), each macro's canonical full-expansion snapshot plus its genuinely distinct variants live only in the concept target that owns the macro's feature. The Snapshots section is the central place to find them: link each snapshot file, describe which expansion variant it captures (the plain case, a supertrait-and-default-method case, a lifetime-and-type-parameter case, a namespace-prefix case, and so on), and then state plainly which variants have *no* snapshot yet, so a gap in coverage is visible rather than silently absent. When you add a snapshot, register it here; when a variant you know matters is untested, record it as a missing snapshot rather than leaving the section to imply full coverage. - -## Known issues - -Record limitations, surprising behavior, and confirmed bugs in the implementation under a **Known issues** section, following the same rule as the reference documents in [../AGENTS.md](../AGENTS.md): describe the behavior as it currently is even when it is wrong, say what the correct behavior would be for a bug, and remove the note in the same change that fixes the code. An implementation document is the right home for a limitation that is about *how the macro is built* — a parser that panics on an input it should reject with a `syn::Error`, a generic-parameter shape the codegen does not yet handle, a stage that drops information — while the reference document's Known issues covers the user-visible consequence. When both exist, cross-link them rather than duplicating the explanation. A failure case captured in `cgp-macro-tests` (per [crates/tests/AGENTS.md](../../crates/tests/AGENTS.md)) should be indexed from the relevant Known issue here and from its Tests section. - -## Documenting how a macro's expansion can fail to compile - -Because a CGP macro expands to ordinary Rust that the compiler checks afterward, a full picture of a macro includes the ways its *accepted* input can still fail to compile downstream — a dimension distinct from the input a macro rejects at parse time. Enumerate these compile-failure modes as part of documenting a macro, and sort each into one of two kinds. This acceptable-versus-problematic split is a *documentation* distinction — whether the failure is a defect — and no longer a directory split: `cargo-cgp` files its UI fixtures by the *quality of the output* it renders (`ok`/`acceptable`/`usability`), while the acceptable/problematic distinction here decides whether a failure is *also* recorded as a defect in the macro's `## Known issues`: - -- An **acceptable** failure is one the macro **intentionally delegates to the Rust compiler** because it lacks the whole-program view the check needs — two `delegate_components!` blocks that map the same key into overlapping impls, a lazily-wired provider whose impl-side dependency the context does not satisfy, or an ill-formed per-entry generic the compiler rejects as unconstrained. This is correct behavior, not a bug, so it does *not* belong under Known issues. Record it in a dedicated **`## Failure modes`** section, kept separate precisely so a reader never mistakes it for a defect: describe *what the compiler rejects and why the macro cannot (or should not) catch it earlier*, show a short code snippet of the failing input, and link the covering class in the [error catalog](../errors/README.md) and its backing `cargo-cgp` UI fixture (as a GitHub URL) rather than a now-removed local fixture. [entrypoints/delegate_components.md](entrypoints/delegate_components.md) is the model. -- A **problematic** failure is a **defect**: input the macro should have rejected with a spanned error, or an expansion that emits invalid Rust. Record it under **`## Known issues`**, alongside the construct's other bugs, with the current (wrong) behavior, the correct behavior, and a short code snippet of the offending input; note the user-visible consequence in the reference document's Known issues and cross-link; and index the pinning fixture from the Tests section. Remove the note when the defect is fixed, per the synchronization rule. - -Give every documented case, in either section, a short illustrative snippet of the CGP code that triggers it (and the offending expansion fragment when it clarifies the failure), following the code-snippet guidance above. Whenever you add or bless a `cargo-cgp` UI fixture for one of these classes — following [cargo-cgp's UI-test guide](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md) — or reclassify a failure, update the [error catalog](../errors/README.md) class and the owning macro's implementation document in the same change so its Failure modes, Known issues, and Tests sections stay the canonical index of how that macro can fail to compile. This is the "enumerate every way the output can expand" review concern (see the macro review workflow in [../../AGENTS.md](../../AGENTS.md)) written down as documentation, so a later agent inherits the failure map rather than rediscovering it. - -The reader-facing documentation of these post-codegen failures — organized by the *kind* of error a user sees rather than by macro — is being consolidated into the [error catalog](../errors/README.md) under `docs/errors/`, which is becoming the canonical home for the acceptable-failure prose that currently lives in these documents' `## Failure modes` sections. The migration proceeds class by class (the catalog's README tracks status). As a class moves, its `## Failure modes` prose is replaced by a cross-link to the catalog, and its fixtures cross-link to the catalog rather than here; the implementation document keeps the *mechanics* (the error-span behavior, the pipeline detail) and, for a **problematic** defect, retains the `## Known issues` explanation of why it is a bug and what the fix would be, cross-linked to the catalog's entry for the observable error. Macro-*rejection* cases (the `assert_macro_rejects` cases in `cgp-macro-tests`) are unaffected and stay here in full. - -## Keeping the reference and implementation documents aligned - -The reference and implementation documents are two views of the same macro and must not drift apart or duplicate each other. The reference owns the user-facing contract — syntax, the expansion shown as before/after Rust, and user-visible corner cases — and points only at library source. The implementation document owns the internal mechanics — the pipeline, the synthesizing functions, the AST types, and all test and snapshot pointers. A reference document may link to an implementation document to elaborate a corner case it does not want to explain in full, and every reference document's Source section links to its implementation counterpart so an agent can drop from "what it does" into "how it is built"; the implementation document links back to the reference for the user-facing semantics rather than re-deriving them. When you change a macro, update both views in the same change: the reference Expansion for the contract, and the implementation Pipeline and Generated items for the mechanics. diff --git a/docs/implementation/README.md b/docs/implementation/README.md deleted file mode 100644 index b07a06fd..00000000 --- a/docs/implementation/README.md +++ /dev/null @@ -1,141 +0,0 @@ -# CGP Implementation Reference - -This directory documents the *internals* of the CGP macros — how each macro is implemented in [crates/macros/cgp-macro-core](../../crates/macros/cgp-macro-core) and [crates/macros/cgp-macro-lib](../../crates/macros/cgp-macro-lib), including corner-case behavior, known limitations and bugs, and the test suite that exercises each construct. It is the documentation an agent reviewing or maintaining the macro source reads first: it records the current state of the code in one place so an agent can pick up a construct's implementation from where the last one left off. The authoring rules, document templates, and the synchronization rule that binds these documents to the code live in [AGENTS.md](AGENTS.md). - -These documents complement the [construct reference](../reference/README.md) rather than repeating it. The reference explains what each construct does for a *user* — its accepted syntax and the code it expands to — and points only at library source. The implementation documents explain how the macro *produces* that behavior, and they are the sole home for every pointer into the [test suite](../../crates/tests): behavioral tests, failure cases, and macro-expansion snapshots all index from here. A reference document links to its implementation counterpart to elaborate a corner case; an implementation document links back to the reference for the user-facing semantics. - -Before reading any single document, skim [Cross-cutting implementation notes](#cross-cutting-implementation-notes) at the end of this file. It records the recurring, easy-to-misread mechanics — the leading-generic insertion that survives lifetimes, how the generic kinds are kept apart, spans, parsing, and hygiene — that a reviewer needs in mind for every macro. - -## Directory layout - -The tree is organized by the kind of source construct each document describes, so an agent has an obvious place to start for "the macro entry point", "the AST type behind a stage", "a helper function", or "an internal macro". A new document goes in the matching subdirectory and registers itself in the catalog below in the same change. - -The [entrypoints/](entrypoints/) directory holds one document per CGP macro — the top-level procedural macro a programmer invokes — describing its `cgp-macro-lib` entry function, the transform pipeline it drives, the items it emits, its corner cases, known issues, tests, and snapshots. The [asts/](asts/) directory holds one document per evaluation stack of AST constructs — the `cgp-macro-core` types implementing `Parse` or `ToTokens`, or serving as an intermediate representation — with the types of one pipeline grouped into a single document. The [functions/](functions/) directory holds the standalone helper functions, split into [functions/parse/](functions/parse/) for parsing helpers and [functions/derive/](functions/derive/) for code-synthesis helpers. The [macros/](macros/) directory holds the internal `macro_rules!` macros the implementation is written in, such as `parse_internal!` and `define_keyword!`. - -## Catalog - -This section is the index of implementation documents. When you add a document, register it here in the same change. - -### Entrypoints — [entrypoints/](entrypoints/) - -The component and provider macros: - -- [`#[cgp_component]`](entrypoints/cgp_component.md) — the foundational component-definition macro and its `preprocess → eval → to_items` pipeline. -- [`#[cgp_impl]`](entrypoints/cgp_impl.md) — lowers consumer-style syntax into a provider impl and hands it to `#[cgp_provider]`. -- [`#[cgp_provider]`](entrypoints/cgp_provider.md) — passes a provider-trait impl through and derives its `IsProviderFor` impl. -- [`#[cgp_new_provider]`](entrypoints/cgp_new_provider.md) — `#[cgp_provider]` with the provider struct also declared. - -Functions and getters: - -- [`#[cgp_fn]`](entrypoints/cgp_fn.md) — a single-implementation capability as a blanket-impl trait, with `#[implicit]` argument lowering. -- [`#[cgp_getter]`](entrypoints/cgp_getter.md) — a getter component wired through CGP, adding `UseField`/`UseFields` provider impls. -- [`#[cgp_auto_getter]`](entrypoints/cgp_auto_getter.md) — a getter as a blanket impl over `HasField`. - -Abstract types and blanket traits: - -- [`#[cgp_type]`](entrypoints/cgp_type.md) — an abstract-type component, reusing the `#[cgp_component]` pipeline and adding `UseType`. -- [`#[blanket_trait]`](entrypoints/blanket_trait.md) — a blanket impl generated from a trait with default methods. - -Wiring and checking: - -- [`delegate_components!`](entrypoints/delegate_components.md) — the context wiring table and its mapping/statement grammar. -- [`check_components!`](entrypoints/check_components.md) — compile-time wiring assertions. -- [`delegate_and_check_components!`](entrypoints/delegate_and_check_components.md) — wire and check in one macro. -- [`cgp_namespace!`](entrypoints/cgp_namespace.md) — reusable, inheritable wiring tables via `RedirectLookup`. - -Type-level construction macros: - -- [`Symbol!`](entrypoints/symbol.md), [`Product!`](entrypoints/product.md), [`Sum!`](entrypoints/sum.md), [`Path!`](entrypoints/path.md) — the type-level string, list, sum, and path macros. - -Data derives: - -- [`#[derive(HasField)]`](entrypoints/derive_has_field.md), [`#[derive(HasFields)]`](entrypoints/derive_has_fields.md) — field-access derives. -- [`#[derive(CgpData)]`](entrypoints/derive_cgp_data.md), [`#[derive(CgpRecord)]`](entrypoints/derive_cgp_record.md), [`#[derive(CgpVariant)]`](entrypoints/derive_cgp_variant.md) — the extensible-data derives. -- [`#[derive(BuildField)]`](entrypoints/derive_build_field.md), [`#[derive(ExtractField)]`](entrypoints/derive_extract_field.md), [`#[derive(FromVariant)]`](entrypoints/derive_from_variant.md) — builder/extractor/variant support. - -Handlers and other extra macros: - -- [`#[cgp_computer]`](entrypoints/cgp_computer.md), [`#[cgp_producer]`](entrypoints/cgp_producer.md) — define `Computer`/`Producer` providers from functions. -- [`#[cgp_auto_dispatch]`](entrypoints/cgp_auto_dispatch.md) — generate a dispatching handler. -- [`#[async_trait]`](entrypoints/async_trait.md) — rewrite trait `async fn` to `-> impl Future`. -- [The `snapshot_*!` family](entrypoints/snapshot_macros.md) — the `cgp-macro-test-util` macros that pin macro expansions as `insta` snapshots. - -### AST stacks — [asts/](asts/) - -One document per evaluation stack, grouped by the macro that owns it: - -- [cgp_component](asts/cgp_component.md), [cgp_impl](asts/cgp_impl.md), [cgp_provider](asts/cgp_provider.md), [cgp_type](asts/cgp_type.md), [cgp_fn](asts/cgp_fn.md), [cgp_getter](asts/cgp_getter.md), [blanket_trait](asts/blanket_trait.md). -- [delegate_component](asts/delegate_component.md), [check_components](asts/check_components.md), [namespace](asts/namespace.md). -- [cgp_data](asts/cgp_data.md) — the shared extensible-data derive stack. -- [product](asts/product.md), [sum](asts/sum.md), [path](asts/path.md), [symbol](asts/symbol.md) — the type-level construction stacks. -- [attributes/](asts/attributes/README.md) — the modifier-attribute AST stacks, one page per modifier: [`#[uses]`](asts/attributes/uses.md), [`#[use_type]`](asts/attributes/use_type.md), [`#[use_provider]`](asts/attributes/use_provider.md), [`#[extend]`](asts/attributes/extend.md), [`#[extend_where]`](asts/attributes/extend_where.md), [`#[derive_delegate]`](asts/attributes/derive_delegate.md), and [`#[default_impl]`](asts/attributes/default_impl.md). - -### Functions — [functions/](functions/) - -The cross-cutting helper functions; construct-specific parse/derive helpers are documented inside the owning macro's entrypoint or AST document. - -- [Delegated-impl synthesis](functions/derive/delegated_impls.md) — the forwarding-impl machinery shared by the component impls. -- [`parse_is_provider_params`](functions/parse/is_provider_params.md) — building the `IsProviderFor` params tuple from trait generics. -- [`merge_generics`](functions/derive/generics.md) — combining two `Generics` into one. -- [Identifier case conversion](functions/derive/idents.md) — the PascalCase/snake_case/reserved-name helpers. - -### Internal macros — [macros/](macros/) - -- [`parse_internal!`](macros/parse_internal.md) — build a `syn` node from quoted tokens with a descriptive parse error. -- [`define_keyword!`](macros/define_keyword.md) — declare a custom-keyword marker type implementing `IsKeyword`. -- [`export_construct!` / `export_constructs!`](macros/export_constructs.md) — declare the hygienic markers backing `exports.rs`. - -## Cross-cutting implementation notes - -The macros share a handful of mechanics that are easy to misread and that recur across many constructs, so a reviewer should hold them in mind before reading any single document. This section explains *why the code behaves as it does*; the review workflow in [../../AGENTS.md](../../AGENTS.md) under "Scrutinize the macro codegen" lists *what to check* against these same mechanics for each macro, and the per-construct documents show where each one bites. - -### Generic-parameter insertion and lifetime ordering - -Almost every component and provider macro turns a trait or impl "inside out" by inserting the context — or the provider — as the *leading* generic parameter, and it does so with `insert(0, …)`, pushing the new parameter to the front of a parameter or argument list. This is the single detail that most often misleads a reader, whether an AI agent or a human: Rust requires lifetime parameters to come *before* type and const parameters, so inserting a type at position 0 looks like it places the type ahead of an existing lifetime and emits invalid Rust — `impl` or `Trait`, which the compiler rejects with `E0747`. The insertion is nonetheless correct, and understanding why prevents both a wrong "bug report" against working code and a real bug in new code that skips the step that saves it. - -It works because the built tokens are always emitted through `syn`, which re-orders lifetimes to the front. There are two paths, one for each kind of list the macros insert into: - -- When the target is a **`syn::Generics`** — the `impl<…>` parameter list — `Generics::to_tokens` itself re-emits lifetimes ahead of type and const parameters. Inserting `__Context__` at position 0 of `<'a, T>` leaves the `Punctuated` in the order `[__Context__, 'a, T]`, yet the emitted tokens are `<'a, __Context__, T>`. -- When the target is a **type-argument list** — the `<…>` in `Foo<…>` — the code builds the list with CGP's own `TypeArgs`, whose `to_tokens` does *not* re-order, and then round-trips it through `parse_internal(path.to_token_stream())` into a `syn::Path`. The `syn::AngleBracketedGenericArguments` behind that path re-emits lifetimes first, so the ordering is normalized by the re-parse rather than at insertion. - -The `#[cgp_impl]` lifetime case makes both effects concrete. Writing - -```rust -#[cgp_impl(new GetReference)] -impl<'a> ReferenceGetter<'a, str> for App<'a> { /* … */ } -``` - -builds the provider trait path as `ReferenceGetter, 'a, str>` — the context inserted ahead of the lifetime — but the `parse_internal` round-trip emits it as `ReferenceGetter<'a, App<'a>, str>`, and the impl generics likewise emit lifetimes-first. - -The caveat is the reason both paths matter: **CGP's `TypeArgs` (and its `to_tokens_angle_bracketed`) does not re-order on its own.** Position-0 insertion into a `TypeArgs` is safe *only* because a `syn` re-parse follows it; a new code path that inserts into a `TypeArgs` and emits it directly, without that round-trip, would keep the invalid `` ordering and produce `E0747`. When adding such a site, either round-trip the tokens through `parse_internal` or insert after the lifetimes. - -Every insertion site carries an inline comment flagging this; grep `cgp-macro-core` for `insert(0` to find them. The canonical examples are `to_raw_item_impl` in the [`cgp_impl` stack](asts/cgp_impl.md) for the provider macros, and the provider-trait and consumer-blanket-impl builders in the [`cgp_component` stack](asts/cgp_component.md) for `#[cgp_component]`. - -### Generics: keep the kinds and roles distinct - -Generics are the most error-prone part of the codegen beyond insertion order, because a parameter's *kind* — lifetime, type, or const — and its *role* — an `impl` parameter versus the `` in `Foo` — each need different handling, and mixing them emits subtly wrong output. CGP keeps them in distinct types: `syn::Generics` for impl-position parameters, and its own `TypeGenerics`/`TypeArgs`/`TypeArg` types (under `cgp-macro-core/src/types/ident` and `types/generics`) for argument-position lists. A review should confirm a parameter never crosses from one to the other in the wrong form — a type parameter emitted where an argument belongs, or a lifetime dropped into an argument tuple as if it were a type. - -A few rules recur across the suite and are worth stating plainly. A lifetime is lifted into `Life<'a>` wherever it must stand in type position — inside the `IsProviderFor` params tuple, built by [`parse_is_provider_params`](functions/parse/is_provider_params.md), and inside a provider struct's `PhantomData`. A const argument is rejected in a provider trait's own argument list, because it cannot key the type-based `IsProviderFor` tuple, yet it flows through untouched as a const generic on the provider *struct*. Parameters merged from two sources — the trait's own generics plus an inserted context, say — pass through [`merge_generics`](functions/derive/generics.md) so they cannot collide. Finally, every parameter that appears in a generated header must be bound in that header, or the compiler reports the free parameter as `E0207`. - -### Spans: aim generated items at the token the user wrote - -A generated item's tokens carry whatever span the macro stamps on them, and by default `parse_internal!`/`quote!` stamp every token they synthesize with the macro's `call_site` span — the whole invocation. Placing those spans correctly matters for two separate tools, and a stray `call_site` span misleads each in its own way: the **compiler** aims an error caret at the wrong place, and **rust-analyzer** resolves go-to-definition to the wrong place. Both symptoms trace to the same cause — a token that should carry the narrow span of what the user wrote instead carries `call_site` — so the macros re-span their generated tokens onto the originating user token. A reviewer must weigh both consequences, not just the error carets, because only the carets are pinned by a test. - -The compiler derives an item's error span by joining that item's first and last tokens. A coherence conflict (`E0119`) between two generated impls, an unsatisfied bound, or a name-resolution failure on a generated header therefore underlines the *entire macro block* whenever the item sits at `call_site`, saying nothing about which entry to fix. The [`override_item_span`](../../crates/macros/cgp-macro-core/src/functions/override_span.rs) helper corrects this by re-spanning only the item's two *boundary* tokens — its leading `impl` keyword and its trailing `{ … }` body — onto the entry the user wrote; joining just those two collapses the caret onto the entry. It moves a boundary token only when that token is itself synthesized, telling a synthesized token from a user one by source text: a `call_site` token's source text is the whole invocation, which no narrower user token can equal. - -rust-analyzer maps a source token to its expanded counterpart purely by source range, ignoring hygiene, so a token re-spanned onto a user token's range becomes, as far as the editor is concerned, that user token — and go-to-definition on it offers every construct that now shares the range. Two distinct `call_site` leaks cause this, and both are subtle because the compiler is indifferent to them: - -- **Re-spanning a resolvable reference onto the entry.** If `override_item_span` moved the whole impl rather than its boundary, a synthesized reference such as `IsProviderFor` or `DelegateComponent` would land on the delegate key's range, and a jump on that key would offer those traits alongside the component the user meant. This is exactly why the helper moves only the boundary — a keyword and a delimiter, never a reference — and leaves the interior at its own spans. Leaving the interior alone is also what keeps a wired provider navigable and keeps a per-entry generic's unconstrained-parameter `E0207` pointing at the `` the user wrote. -- **A derived name stamped with `call_site`.** The `#[cgp_component]` marker struct `{Provider}Component`, when its name is not given explicitly, is derived from the provider identifier. Deriving it with `Span::call_site()` gave the generated `struct {Provider}Component` a definition span covering the whole `#[cgp_component(..)]` attribute — a range that also contains the `cgp_component` macro path — so go-to-definition on a delegate key naming that component offered both the component and the macro. Deriving the name from the provider identifier's own span, the way [`derive_check_trait_ident`](../../crates/macros/cgp-macro-core/src/types/check_components/table.rs) already spans its derived check trait, points the definition at a single user token and removes the ambiguity. It surfaced only across crates: within the defining crate rust-analyzer resolves from the live macro expansion, whereas a dependent crate relies on the recorded definition span, which is where the leak shows. - -Two more re-spanning techniques cover the rest. `override_item_span`'s sibling `override_span` re-spans *every* token unconditionally, which is right only for `check_components!`, where the intent is to clobber one shared user token — the context type — onto each checked component in turn. Where the originating token is itself synthesized and has lost its span (an `@`-path's `PathCons<..>` nest), the evaluated form carries an explicit span field forward, as `EvaluatedCheckEntry.span` and `EvaluatedDelegateEntry.span` do; and the provider macros reuse the user's own `for` token so the middle of a generated impl header never falls on `call_site`. - -Only the compiler side is pinned by a test: a `trybuild` `.stderr` fixture records the exact line and column of each caret, so an error-span regression changes a snapshot. The rust-analyzer behavior has no fixture and must be checked by hand in an editor — and checked across crates as well as within one, since the derived-name leak above is invisible from inside the defining crate. The per-macro detail lives in each document's error-spans discussion; [`delegate_components!`](entrypoints/delegate_components.md) and [`#[cgp_impl]`](entrypoints/cgp_impl.md) are the worked examples. - -### Parsing: build with `parse_internal!`, and distrust `syn`'s leniency - -Build every `syn` node from quasi-quoted tokens with [`parse_internal!`](macros/parse_internal.md) rather than `parse2` or `parse_quote!`, so a malformed fragment fails with an error naming the target type and the offending tokens instead of a bare parse error or a panic that aborts the compiler. A function that parses anything should thread `syn::Result` and propagate the error; reserve the panicking `parse_quote!` for tokens that are trivially, locally guaranteed to parse. The other half of this concern is that **`syn` parses far more leniently than Rust accepts** — it takes a type argument before a lifetime, a turbofish where CGP wants none, and associated-type bindings in plain argument positions — so CGP defines its own restricted argument types — `TypeArg`, `TypeArgs`, and `PathWithTypeArgs` — to reject at parse time what `syn` would silently wave through, and a review should confirm user-facing input is validated against those rather than raw `syn` nodes. The same leniency is what makes the lifetime round-trip above work: `syn` accepts the mis-ordered `` on parse and quietly re-emits it correctly. - -### Hygiene: exports markers and reserved identifiers - -The expansion must compile in a crate that has only `cgp` in scope, so every CGP item it references is emitted through a [`crate::exports`](macros/export_constructs.md) marker that resolves to `::cgp::macro_prelude::`, never as a bare or hand-written path; a review greps the codegen for any CGP name not interpolated from an `exports` marker. The reserved identifiers the expansion introduces — `__Context__`, `__Provider__`, `__Component__`, `__Components__`, `__context__`, and the like — are wrapped in double underscores so they cannot collide with a user's own names, and a new identifier the codegen invents should follow the same convention. diff --git a/docs/implementation/asts/attributes/README.md b/docs/implementation/asts/attributes/README.md deleted file mode 100644 index bc338905..00000000 --- a/docs/implementation/asts/attributes/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# The attribute-modifier AST stacks - -The attribute modifiers — `#[uses]`, `#[use_type]`, `#[use_provider]`, `#[extend]`, `#[extend_where]`, `#[derive_delegate]`, and `#[default_impl]` — are not standalone macros; each is an `#[…]` attribute that a host macro strips off its input, parses into an AST type, and folds into the code it generates. This directory documents one modifier per page: each page covers that modifier's AST types, what it parses from, and what it injects into its host's output. This overview covers what the modifiers share — how a host collects them and which host accepts which — so the per-modifier pages can stay focused on their own types. For the user-facing syntax and expansion of each, read the reference documents in the [reference `attributes/` subdirectory](../../../reference/attributes/uses.md); for how the hosts drive them, see [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md), [entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md), and [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). - -## The pages - -Each modifier has its own page in this directory: - -- [`#[uses]`](uses.md) — import `Self` trait bounds onto a provider impl, reading like a `use` statement. -- [`#[use_type]`](use_type.md) — import an abstract associated type: rewrite the bare alias everywhere and add the owning trait as a bound. -- [`#[use_provider]`](use_provider.md) — complete an inner provider's bound for a higher-order provider. -- [`#[extend]`](extend.md) — add *supertrait* bounds to a generated trait. -- [`#[extend_where]`](extend_where.md) — add `where` predicates to a generated trait definition. -- [`#[derive_delegate]`](derive_delegate.md) — generate a `UseDelegate` dispatcher provider impl for a component. -- [`#[default_impl]`](default_impl.md) — register a provider as a namespace's per-path default. - -## How a host collects a modifier - -The modifiers do not parse themselves out of the token stream on their own; a host collects them first. Each host macro owns a collector type that walks the item's attribute list once, matches the leading identifier (`uses`, `use_type`, …) of every attribute, parses that attribute's arguments into the corresponding AST type, and passes any unrecognized attribute through untouched onto the generated code. There are three collectors, one per host: - -- `CgpComponentAttributes` (in `cgp_component_attributes.rs`) collects the modifiers `#[cgp_component]` accepts, during its `preprocess` stage. -- `CgpImplAttributes` (in `cgp_impl_attributes.rs`) collects the modifiers `#[cgp_impl]` accepts, during `ItemCgpImpl::lower`. -- `FunctionAttributes` (in `function.rs`) collects the modifiers `#[cgp_fn]` accepts (and the getter macros reuse), during `preprocess`. - -An unrecognized attribute is never an error: a collector that does not match an attribute's leading identifier pushes it back onto a `raw_attributes` list (or straight back onto the item, for the component collector), which the host re-attaches to the generated code. This is what lets `#[async_trait]`, `#[allow(...)]`, and any other foreign attribute ride through a host macro untouched. - -## Which host accepts which modifier - -Which modifiers a host accepts differs, because a modifier is only meaningful on the construct that can consume it — `#[derive_delegate]` needs a component's provider trait to dispatch, `#[default_impl]` needs a provider to register, and `#[extend_where]` needs a generated trait whose own `where` clause it can extend. A modifier therefore appears only in the collectors of the hosts that consume it: - -| Modifier | `#[cgp_component]` | `#[cgp_impl]` | `#[cgp_fn]` | -|---|:---:|:---:|:---:| -| `#[uses]` | | ✓ | ✓ | -| `#[use_type]` | ✓ | ✓ | ✓ | -| `#[use_provider]` | | ✓ | ✓ | -| `#[extend]` | ✓ | | ✓ | -| `#[extend_where]` | | | ✓ | -| `#[derive_delegate]` | ✓ | | | -| `#[default_impl]` | | ✓ | | - -Two further modifiers the same collectors handle are documented elsewhere rather than here, because they belong to another construct's story. `CgpComponentAttributes` also collects `#[prefix(@path in Namespace)]`, which registers a component into a namespace and is documented with the [namespace machinery](../namespace.md). `FunctionAttributes` also collects `#[impl_generics(Param: Bound)]`, which adds a bounded generic parameter to a `#[cgp_fn]`'s impl alone and is documented with [`#[cgp_fn]`](../../entrypoints/cgp_fn.md). - -## Source - -- The modifiers live in [cgp-macro-core/src/types/attributes/](../../../../crates/macros/cgp-macro-core/src/types/attributes/), one module or submodule per modifier, re-exported from `mod.rs`. -- The host collectors are `CgpComponentAttributes` in [cgp_component_attributes.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_component_attributes.rs), `CgpImplAttributes` in [cgp_impl_attributes.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs), and `FunctionAttributes` in [function.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs). diff --git a/docs/implementation/asts/attributes/default_impl.md b/docs/implementation/asts/attributes/default_impl.md deleted file mode 100644 index a5757da2..00000000 --- a/docs/implementation/asts/attributes/default_impl.md +++ /dev/null @@ -1,48 +0,0 @@ -# `#[default_impl]` — the AST stack - -`#[default_impl(@test.ShowImplComponent.u32 in ExtendedNamespace)]` on a `#[cgp_impl]` provider registers that provider as a namespace's default for one path. It is a modifier attribute collected by the impl host; this page covers its AST types and the registration impl it builds, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and the namespace machinery it plugs into, read the reference document [`DefaultNamespace`](../../../reference/traits/default_namespace.md). - -## `DefaultImplAttribute` - -The attribute parses into a `DefaultImplAttribute` — a `key_type` (a `UniPathOrType`, so the key may be a path or a type), the `in` keyword, and a `namespace` path (a `PathWithTypeArgs`). Parsing reads the key type, the `in` token, and the namespace in order. - -## `DefaultImplAttributes` - -`DefaultImplAttributes` is the collection wrapper — a `Vec` — that `CgpImplAttributes` fills, one entry per `#[default_impl]` attribute on the provider block. Its `to_item_impls(provider_generics, provider_type)` maps each entry through `to_item_impl`, and the host (`#[cgp_impl]`) emits the resulting impls after the provider impl, using the provider's own generics and provider type. - -## `to_item_impl` — the registration impl - -`DefaultImplAttribute::to_item_impl(provider_generics, provider_type)` emits one impl of the namespace's lookup trait, keyed on the given path type, whose `Delegate` associated type is the provider being defined: - -```rust -// #[default_impl(@test.ShowImplComponent.u32 in ExtendedNamespace)] on provider ShowU32: -impl<__Components__> ExtendedNamespace<__Components__> -for PathCons>> -{ - type Delegate = ShowU32; -} -``` - -The namespace path gains a trailing `__Components__` type argument and the impl generics gain a matching `__Components__` parameter, so the default is generic over any table the namespace is queried through. The impl is built from quasi-quoted tokens and then re-spanned onto the user-written key with [`override_item_span`](../../entrypoints/delegate_components.md#error-spans), so a coherence conflict (`E0119`) between two default impls for the same key is reported on the key inside `#[default_impl(Key in …)]` rather than on the whole `#[cgp_impl]` attribute; only the boundary moves, so the interior tokens stay navigable in an IDE. - -## The dropped `where` clause - -**The provider's `where` clause is deliberately dropped from this impl**, and this is the subtle correctness point of `to_item_impl`. It receives the provider impl's generics *after* `#[implicit]`/`#[uses]`/`#[use_type]`/`#[use_provider]` have pushed their `Self`-keyed impl-side bounds into it — a provider with `#[use_type(HasErrorType.Error)]`, for instance, arrives carrying `where Self: HasErrorType`. Those bounds belong on the provider's own impl and its `IsProviderFor`, never on this registration impl, whose only job is `type Delegate = Provider`. The registration impl's `Self` is the path key (`PathCons<..>`), so a retained `Self: HasErrorType` would demand `PathCons<..>: HasErrorType` — a bound that never holds — and silently break every context that joins the namespace. `to_item_impl` therefore clears `generics.where_clause` before splitting, keeping only the parameters that name the key and provider plus the `__Components__` table. - -The one consequence of dropping the `where` clause is a limitation on generic providers: a provider whose *type* is generic, and whose parameter appears only in the `Delegate` associated type, would leave that parameter unconstrained once the clause is gone, so a per-component default is written for a concrete provider rather than a generic one. - -## Tests - -The behavioral and snapshot tests exercise the emitted impl, the wiring it enables, the `where`-clause drop, and the cross-crate orphan restriction: - -- [namespaces/default_impls.rs](../../../../crates/tests/cgp-tests/tests/namespaces/default_impls.rs) pins the emitted namespace-default impl (`snapshot_cgp_impl!`), and [namespaces/default_impls_wiring.rs](../../../../crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs) checks a context picks up the default. -- [namespaces/default_impl_use_type.rs](../../../../crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs) pins that the registration impl carries no `where` clause when the provider has a `#[use_type]` dependency, and resolves such a provider through a context that joins the namespace. -- The cross-crate orphan restriction on a default is pinned by the `cargo-cgp` UI fixtures [`usability/wiring/orphan/default_impl_foreign_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/default_impl_foreign_prefix_path.rs) (a *prefixed* component's foreign path key) and [`usability/wiring/orphan/default_impl_foreign_component.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/default_impl_foreign_component.rs) (a foreign *unprefixed* component marker key), both the [orphan-rule violation](../../../errors/wiring/orphan-rule.md) class. - -The duplicate-key conflict `#[cgp_impl]` defers to the compiler is covered on the host's own page — see [Failure modes in entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md#failure-modes). - -## Source - -- The `default_impl/` submodule in [cgp-macro-core/src/types/attributes/default_impl/](../../../../crates/macros/cgp-macro-core/src/types/attributes/default_impl/): `attribute.rs` holds `DefaultImplAttribute`, its parser, and `to_item_impl`; `attributes.rs` holds the `DefaultImplAttributes` collection and `to_item_impls`. -- Boundary re-spanning is [`override_item_span`](../../../../crates/macros/cgp-macro-core/src/functions/override_span.rs). -- The host that drives it: [entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md). diff --git a/docs/implementation/asts/attributes/derive_delegate.md b/docs/implementation/asts/attributes/derive_delegate.md deleted file mode 100644 index 14c431a3..00000000 --- a/docs/implementation/asts/attributes/derive_delegate.md +++ /dev/null @@ -1,44 +0,0 @@ -# `#[derive_delegate]` — the AST stack - -`#[derive_delegate(UseDelegate)]` on a `#[cgp_component]` trait generates a dispatcher provider impl, so the component can be wired to a `UseDelegate` table that dispatches on a generic parameter. It is a modifier attribute collected by the component host; this page covers its AST types and the impl it builds, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/derive_delegate.md](../../../reference/attributes/derive_delegate.md). - -## `DeriveDelegateAttribute` - -The attribute parses into a `DeriveDelegateAttribute` — a `wrapper` identifier (`UseDelegate`) and its angle-bracketed key, held as a `Punctuated` of `params`. The parser reads the wrapper identifier, a `<`, then either a single identifier or a parenthesized tuple of identifiers, then a `>`. An empty parenthesized tuple is rejected with a spanned "expect non-empty tuple list of identifiers in use_delegate_spec" error, so `UseDelegate<()>` cannot slip through as a keyless dispatcher. - -## `DeriveDelegateAttributes` - -`DeriveDelegateAttributes` is the thin collection wrapper — a `Vec` — that `CgpComponentAttributes` fills, one entry per `#[derive_delegate]` attribute on the trait. The host emits one dispatcher impl per entry alongside the component's standard provider impls, during `to_items`. - -## `to_provider_impl` — the generated impl - -`DeriveDelegateAttribute::to_provider_impl(provider_trait)` builds one impl of the provider trait for `Wrapper<__Components__>` that forwards each method to a delegate looked up through `DelegateComponent`. It clones the provider trait's own generics and appends two synthetic parameters — `__Components__` (the table type) and `__Delegate__` (the resolved delegate) — then adds two `where` bounds: the table lookup that resolves the key to a delegate, and the delegate's own provider-trait bound. Each trait method is forwarded through the shared [delegated-impl helpers](../../functions/derive/delegated_impls.md), so the dispatcher's bodies read as `<__Delegate__>::method(context, …)`: - -```rust -impl<__Context__, __Components__, __Delegate__> AreaCalculator<__Context__> - for UseDelegate<__Components__> -where - __Components__: DelegateComponent<(Shape), Delegate = __Delegate__>, - __Delegate__: AreaCalculator<__Context__>, -{ /* each method forwards to __Delegate__ */ } -``` - -The key in the `DelegateComponent<(…)>` lookup is the parenthesized `params` the attribute parsed, so a single-identifier key becomes `(Shape)` and a tuple key `(A, B)`. The impl keeps the component's own generics ahead of the two synthetic parameters, and reuses the provider trait's type generics (via `split_for_impl`) for both the delegate bound and the forwarded projections. - -## Behavior and corner cases - -**`#[derive_delegate]` is a legacy form for user code, but not dead code.** The `open` dispatch statement is preferred for new components, yet CGP's own error and handler families still *define* components with `#[derive_delegate]`, so the dispatcher impl this attribute generates remains in active use across the library. - -**The synthetic parameters use the reserved double-underscore form.** `__Components__` and `__Delegate__` are constructed with `Span::call_site()` and the double-underscore convention so they cannot clash with a user's own type parameters or with the component's generics that precede them. - -## Tests - -- [dispatching/use_delegate_getter.rs](../../../../crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs) wires a component defined with `#[derive_delegate]` through a `UseDelegate` table. - -The `UseDelegate` impl a `#[derive_delegate]` attribute adds to a bare component has no dedicated expansion snapshot yet; it is exercised through the error and handler families instead (noted as a missing snapshot in [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md)). - -## Source - -- The `derive_delegate/` submodule in [cgp-macro-core/src/types/attributes/derive_delegate/](../../../../crates/macros/cgp-macro-core/src/types/attributes/derive_delegate/): `attribute.rs` holds `DeriveDelegateAttribute`, its parser, and `to_provider_impl`; `attributes.rs` holds the `DeriveDelegateAttributes` collection. -- The forwarding method bodies come from the [delegated-impl helpers](../../functions/derive/delegated_impls.md). -- The host that drives it: [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md). diff --git a/docs/implementation/asts/attributes/extend.md b/docs/implementation/asts/attributes/extend.md deleted file mode 100644 index 5277d750..00000000 --- a/docs/implementation/asts/attributes/extend.md +++ /dev/null @@ -1,29 +0,0 @@ -# `#[extend]` — the AST stack - -`#[extend(Trait)]` adds *supertrait* bounds to a generated trait, widening the trait's public interface rather than adding a hidden impl-side dependency. It is a modifier attribute collected by a host macro; this page covers what it parses into and what it injects, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/extend.md](../../../reference/attributes/extend.md). - -## What it parses into - -`#[extend]` has no dedicated AST type: it parses directly into a `Vec` field on its host collector, populated by `Punctuated::::parse_terminated`. On `#[cgp_fn]` that field is `FunctionAttributes::extend`; on `#[cgp_component]` it is `CgpComponentAttributes::extend`. Each bound is a full `syn::TypeParamBound`, so the same wide grammar `#[uses]` accepts parses here. - -## What the hosts inject - -`#[extend]` is accepted on `#[cgp_component]` and `#[cgp_fn]`, and the two hosts treat it differently because a `#[cgp_component]` trait can already declare supertraits natively while a `#[cgp_fn]` trait cannot: - -- On **`#[cgp_component]`**, `preprocess` appends the bounds to the consumer trait's supertraits (`item_trait.supertraits.extend(attributes.extend.clone())`) before the later stages transform the trait. It is the preferred way to add a *non-type* capability supertrait; an abstract-type supertrait should instead use [`#[use_type]`](use_type.md), which adds the bound *and* rewrites the type. -- On **`#[cgp_fn]`**, the bounds are pushed onto *both* the generated trait's supertraits and the impl's `where` clause. This dual placement exists because it is the only way to add a supertrait to a `#[cgp_fn]` trait — a `#[cgp_fn]`'s own `where` clauses are reserved for impl-side dependencies, so there is no other channel through which a supertrait can reach the generated trait. - -The contrast with [`#[uses]`](uses.md) is the reason both exist: `#[uses]` lands its bound on the impl's `Self` alone, hidden from callers, while `#[extend]` makes the bound a supertrait that every caller sees. And the contrast with [`#[extend_where]`](extend_where.md) is placement: `#[extend]` adds a *supertrait* (a bound on the trait's own `Self`), while `#[extend_where]` adds a full `where` predicate that may bound any type. - -## Tests - -The behavioral tests exercise both hosts and the interaction with getters and `#[use_type]`: - -- [impl_side_dependencies/fn_extend.rs](../../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_extend.rs) pins the `#[cgp_fn]` supertrait form. -- [abstract_types/extend_component.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/extend_component.rs) exercises it on a component, and [abstract_types/use_type_fn_extend.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_extend.rs) alongside `#[use_type]`. -- [getters/abstract_type_extend.rs](../../../../crates/tests/cgp-tests/tests/getters/abstract_type_extend.rs) uses it with a getter. - -## Source - -- The `extend` field is on `FunctionAttributes` in [function.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs) and on `CgpComponentAttributes` in [cgp_component_attributes.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_component_attributes.rs). -- The hosts that drive it: [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md) and [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). diff --git a/docs/implementation/asts/attributes/extend_where.md b/docs/implementation/asts/attributes/extend_where.md deleted file mode 100644 index a80e81b8..00000000 --- a/docs/implementation/asts/attributes/extend_where.md +++ /dev/null @@ -1,20 +0,0 @@ -# `#[extend_where]` — the AST stack - -`#[extend_where(Bound)]` adds `where` predicates to a generated trait definition, and is the only modifier that can make an arbitrary bound part of the generated *trait* rather than only its impl. It is `#[cgp_fn]`-only, and it is a modifier attribute collected by the function host; this page covers what it parses into and what it injects, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/extend_where.md](../../../reference/attributes/extend_where.md). - -## What it parses into - -`#[extend_where]` has no dedicated AST type: it parses directly into the `extend_where` field of `FunctionAttributes` — a `Vec` — populated by `Punctuated::::parse_terminated`. Because each entry is a full [`syn::WherePredicate`](https://docs.rs/syn/latest/syn/enum.WherePredicate.html) rather than a `TypeParamBound`, it can bound *any* type, not only `Self`, and can carry associated-type-equality constraints — expressiveness that `#[uses]` and `#[extend]` (which parse `TypeParamBound`s) do not have. - -## What the host injects - -`preprocess` adds the predicates to *both* the generated trait's own `where` clause and the impl's `where` clause. Adding them to the trait is the point of the attribute — it is what distinguishes `#[extend_where]` from [`#[uses]`](uses.md), which adds a bound to `Self` on the impl alone. Where `#[uses]` hides a dependency behind the impl, `#[extend_where]` makes a bound a visible part of the trait interface, and its full-predicate grammar is what lets it express a bound on a type other than `Self`. - -## Tests - -- [abstract_types/use_type_fn_nested_foreign.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs) exercises `#[extend_where]` alongside `#[use_type]` on a `#[cgp_fn]`, where it adds a `Scalar: Copy` bound (rewritten to the two-hop path) to the generated trait's `where` clause. - -## Source - -- The `extend_where` field is on `FunctionAttributes` in [function.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs). -- The host that drives it: [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). diff --git a/docs/implementation/asts/attributes/use_provider.md b/docs/implementation/asts/attributes/use_provider.md deleted file mode 100644 index 4282a543..00000000 --- a/docs/implementation/asts/attributes/use_provider.md +++ /dev/null @@ -1,35 +0,0 @@ -# `#[use_provider]` — the AST stack - -`#[use_provider(Inner: AreaCalculator)]` completes an inner provider's bound for a higher-order provider: the one thing it does is finish the bound by inserting the context as its leading type argument, so the user's `: AreaCalculator` becomes `AreaCalculator`, and move the completed bound onto the impl's `where` clause. It is a modifier attribute collected by a host macro; this page covers its AST types and what it injects, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/use_provider.md](../../../reference/attributes/use_provider.md). - -## `UseProviderAttribute` - -The attribute parses into a `UseProviderAttribute` per entry: a `context_type` (always `Self`), a `provider_type` (the inner provider parameter, e.g. `Inner`), a colon, and a `+`-separated list of provider-trait paths as `provider_trait_bounds` (each a `PathWithTypeArgs`). Parsing is straightforward — the context is fixed to `Self`, then the provider type, the colon, and the terminated `+`-list of bounds. - -The completion happens in two methods. `to_type_param_bounds(context_type)` walks each provider-trait bound, clones it, and **inserts the context at index 0 of the bound's angle-bracketed arguments**, so `AreaCalculator` becomes `AreaCalculator` and a bound that already carries parameters keeps them after the context. Position 0 sits ahead of any lifetime argument, which would be invalid Rust on its own; the method re-parses each completed bound through `parse_internal!`, and that `syn` round-trip re-emits lifetimes first, normalizing the order (see [Generic-parameter insertion and lifetime ordering](../../README.md#generic-parameter-insertion-and-lifetime-ordering)). `to_provider_bounds(context_type)` then wraps the completed bounds into a single `provider_type: bounds` `where` predicate: - -```rust -// #[use_provider(Inner: AreaCalculator)] becomes the where-predicate: -Inner: AreaCalculator -``` - -## `UseProviderAttributes` - -`UseProviderAttributes` holds the `Vec` a host collected and applies them through its `AddTypeParamBounds` impl. `add_type_param_bounds(self_type, generics)` returns early when there are no entries, otherwise pushes each entry's `to_provider_bounds(self_type)` predicate onto the impl generics' `where` clause. The `self_type` passed in is the impl's context type, which is what fills the inserted leading argument. - -## What the hosts inject - -`#[use_provider]` is accepted on `#[cgp_impl]` (collected into `CgpImplAttributes`) and `#[cgp_fn]` (collected into `FunctionAttributes`), and on both it contributes only the completed `where` predicate on the provider parameter. There is no call-site rewriting: the body still calls the inner provider explicitly through the associated-function form (`Inner::area(self)`), so the attribute's whole job is the bound, not the invocation. - -## Tests - -The behavioral tests exercise both hosts and a full higher-order provider: - -- [higher_order_providers/use_provider_fn.rs](../../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_fn.rs) pins the `#[cgp_fn]` form. -- [higher_order_providers/use_provider_impl.rs](../../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs) pins the `#[cgp_impl]` form. -- [higher_order_providers/scaled_area.rs](../../../../crates/tests/cgp-tests/tests/higher_order_providers/scaled_area.rs) wires a full higher-order provider through it. - -## Source - -- The `use_provider/` submodule in [cgp-macro-core/src/types/attributes/use_provider/](../../../../crates/macros/cgp-macro-core/src/types/attributes/use_provider/): `attribute.rs` holds `UseProviderAttribute` and the bound completion, `attributes.rs` holds `UseProviderAttributes` and its `AddTypeParamBounds` impl. -- The hosts that drive it: [entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md) and [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). diff --git a/docs/implementation/asts/attributes/use_type.md b/docs/implementation/asts/attributes/use_type.md deleted file mode 100644 index a7822f6c..00000000 --- a/docs/implementation/asts/attributes/use_type.md +++ /dev/null @@ -1,87 +0,0 @@ -# `#[use_type]` — the AST stack - -`#[use_type(HasErrorType.Error)]` imports an abstract associated type: it rewrites the bare alias (`Error`) everywhere in the host's signatures into its fully-qualified `::Error` form, and adds the owning trait as a bound so those paths are well-formed. It is the richest of the attribute modifiers, with a small stack of AST types and a three-step transform driven off them; this page covers that stack and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/use_type.md](../../../reference/attributes/use_type.md). - -## The stack at a glance - -An import parses into a `UseTypeAttribute` per spec, each carrying one or more `UseTypeIdent` entries, and the specs a host collects are held together in a `UseTypeAttributes`. Application then runs in three steps off `UseTypeAttributes`: **ground** each spec's context, **substitute** every bare alias in one traversal with the `SubstituteAbstractTypes` visitor, and **add the bounds** to the trait or impl. The type-equality (`= T`) pins are derived separately by `derive_use_type_predicates`. The sections below follow the data through those types in order. - -## `UseTypeIdent` - -`UseTypeIdent` is one imported associated type within a spec. It captures three pieces: the `type_ident` (the associated type's name, e.g. `Error`), an optional `as_alias` (the `as NewName` rename), and an optional `equals` (the `= ConcreteType` pin). Its `alias_ident()` returns the alias when one is written and the type identifier otherwise — this is the name that appears bare in the host's signatures and that the substitution matches against. Parsing reads the identifier, then optionally `as `, then optionally `= `, so the grammar is `Ident (as Ident)? (= Type)?`. - -## `UseTypeAttribute` - -`UseTypeAttribute` is one import spec — a trait path, the types imported from it, and the context they are projected against. It holds a `context_type` (the type whose associated type is imported, defaulting to `Self` or set by a trailing `in Context`), the `trait_path` (a `PathWithTypeArgs`, so the owning trait may be a full path or carry generic arguments such as `HasFooType`), and a `Vec` of the types imported from that trait. - -Its parser is where the `.`-versus-`::` separation is decided. It parses the trait path, consumes a `.`, and reads either a single `UseTypeIdent` or a brace-delimited comma list of them (`HasTypes.{A, B as C}`); it then reads an optional `in Context` suffix, parsing `Context` as a `PathWithTypeArgs`, with no suffix defaulting the context to `Self`. The `.` — not `::` — is what separates the trait from the associated type, so a trait that is itself a path keeps its `::` inside `trait_path` while the `.` unambiguously marks where the associated type begins; the `in` keyword, reserved in Rust, marks the context clause just as cleanly since it can never appear inside a type or path: - -```rust -// #[use_type(foo::bar::HasScalarType.Scalar in Context)] -// └────trait_path────┘ └assoc┘ └─ctx─┘ -``` - -`UseTypeAttribute` also carries `replace_ident`, the per-spec lookup the substitution visitor calls: given an identifier, it returns the fully-qualified replacement identifier if the identifier matches one of the spec's `alias_ident()`s, and — importantly — **stamps the user's original span onto the replacement**. Preserving the span is what makes a caret on a mistyped imported type point at the identifier the user wrote rather than at the whole macro block. - -## `UseTypeAttributes` and the three-step transform - -`UseTypeAttributes` holds the `Vec` a host collected and owns the transform that applies them. It exposes two entry points — `transform_item_trait` for a generated trait and `transform_item_impl` for a generated impl — and both run the same three steps, differing only in the bounds they add at the end. Each first calls `forbid_duplicate_aliases` and returns early when there are no specs. - -**Step one — grounding.** `grounded_specs` resolves each spec's context type up front so the later steps agree on one fully-qualified context. An `in Context` whose `Context` is itself imported by another spec — as in `HasTypes.Types, HasScalarType.Scalar in Types` — is rewritten from the bare alias `Types` into `::Types`. Contexts that name a real generic parameter or `Self` are left alone. The pass iterates to a fixpoint, running the substitution visitor over each spec's `context_type` and stopping when a pass makes no change; because each pass grounds one more level, `attributes.len()` passes cover any acyclic chain (so `HasA.A, HasB.B in A, HasC.C in B` grounds `B` to `<::A as HasB>::B`), and a cyclic reference simply stops making progress rather than looping — it surfaces later as an ordinary unresolved-type error. - -**Step two — substitution.** A single `SubstituteAbstractTypes` traversal, holding *every* grounded spec at once, rewrites each bare use of an alias into its fully-qualified associated type: - -```rust -// #[use_type(HasErrorType.Error)] turns a bare `Error` into: -::Error -``` - -Grounding the contexts up front is what lets one pass suffice: the replacement a spec emits already contains no bare alias, so the visitor never revisits its own output, and because aliases are unique (guaranteed by `forbid_duplicate_aliases`) the order among specs is irrelevant. The visitor's matching rules are covered under [SubstituteAbstractTypes](#substituteabstracttypes) below. - -**Step three — adding the bounds.** This is where the two entry points diverge: - -- `transform_item_trait` pushes each `Self`-context spec's trait path onto the consumer trait's *supertraits*, so the abstract type is available to every signature. For a foreign `in Context` spec it instead adds a plain `Context: Trait` predicate to the trait's `where` clause, so the substituted `::Assoc` signatures are well-formed without the author declaring the bound. The type-equality (`= T`) form is an impl-side pin and is deliberately *not* added to a trait here. -- `transform_item_impl` derives the impl-side `where` predicates through `derive_use_type_predicates` and extends the impl's `where` clause with them. These carry the `= T` equality pins as associated-type bindings; the pins are impl-side only and never reach the trait. - -## `derive_use_type_predicates` and the equality pins - -`derive_use_type_predicates` (in `type_predicates.rs`) turns a set of grounded specs into the impl-side `where` predicates they contribute — one `context_type: trait_path` bound per spec, carrying any `= T` pins as associated-type bindings inside the trait path (`Context: Trait`). It reads each spec's already-grounded `context_type` directly rather than re-resolving aliases, which is why grounding must run first. - -The pins can also **unify two imported abstract types**. `find_type_equality` handles the case where an equality's right-hand side names *another* imported alias: given `HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password}`, it recognizes that `Password` is itself an imported alias and rewrites the pin's target into the other spec's fully-qualified projection (`::Password`), so the two abstract types are constrained equal. When the right-hand side is an ordinary concrete type it is used as written. - -Two more functions in this module round out the impl-side logic. `forbid_duplicate_aliases` rejects any two imports that resolve to the same identifier or alias: it flattens every `UseTypeIdent` across all specs into one list and compares every pair by `alias_ident()`, so the check catches a collision across separate specs *and* within a single braced list, uniformly for components, impls, and functions. A shared alias would make the substitution silently pick the first match and drop the rest, so it must be an error. - -## `SubstituteAbstractTypes` - -`SubstituteAbstractTypes` (in [visitors/substitute_abstract_type.rs](../../../../crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs)) is the `VisitMut` that performs the bare-alias rewrite in a single traversal. Holding every spec at once — rather than running one visitor per spec — is what lets one pass over the item handle all imports regardless of the order they were written. It rewrites a type only when the type is a bare, single-segment, argument-free path (`qself: None`, no leading colon, one segment, `PathArguments::None`) whose identifier matches a spec's `alias_ident()`; it then replaces the type with `::replacement_ident` and records `is_changed`. The strict match guard is deliberate: a path that already carries a qualifier, arguments, or more than one segment is not a bare alias and is left untouched, so a genuine `Self::Error` or a generic `Foo` is not disturbed. `is_changed` is what the grounding fixpoint reads to decide whether a further pass would be a no-op. - -## Behavior and corner cases - -**A `=` equality is rejected outright on `#[cgp_component]`.** `CgpComponentAttributes::parse` scans each imported `UseTypeIdent` for an `equals` and returns a spanned "Type equality constraints cannot be used in component trait definition" error, because a component *definition* cannot pin an abstract type to a concrete one — the pin belongs on an impl. The impl and function collectors accept the equality form. - -**A foreign `in Context` bound reaches the trait.** On a component or function trait, `transform_item_trait` adds the `Context: Trait` predicate to the trait's `where` clause for a foreign spec. Without this the constraint would be silently dropped, leaving a signature that only compiles when the author happens to supply the bound elsewhere; adding it is what makes the substituted `::Assoc` paths well-formed by construction. - -**Nested-import order does not matter, but a cycle has no order.** Grounding runs a fixpoint over *all* specs at once, so a spec's `in Context` may name an alias imported by any other spec regardless of where it sits in the list — `HasC.C in B, HasB.B in A, HasA.A` grounds exactly like the front-to-back `HasA.A, HasB.B in A, HasC.C in B`. The one arrangement with no valid order is a cycle, where two contexts resolve through each other (`HasA.A in B, HasB.B in A`). The fixpoint stops after `attributes.len()` passes rather than looping, so the cyclic aliases are never grounded and stay bare in the emitted types; the compiler then reports `E0425` "cannot find type" at the offending `in` alias. CGP could in principle detect the cycle locally and reject it at macro time, but currently lowers it faithfully and defers to the compiler. - -## Tests - -The behavioral tests span every host and every form the attribute accepts: - -- [abstract_types/use_type_component.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs) covers the `#[cgp_component]` supertrait form; [use_type_foreign.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs) the `in Context` foreign form on a component (with an *unbounded* generic parameter, guarding that the foreign bound reaches the trait); and [use_type_auto_getter.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_auto_getter.rs) that a getter macro (`#[cgp_auto_getter]`) accepts `#[use_type]` through the same collector. -- [abstract_types/use_type_fn_alias.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs), [use_type_fn_equality.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs), and [use_type_fn_foreign.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs) cover the alias, equality, and foreign-context (`in`) forms on `#[cgp_fn]` — the last also using an unbounded generic parameter. -- [use_type_fn_equality_cross_trait.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs), [use_type_fn_foreign_equality.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs), [use_type_fn_foreign_equality_cross_trait.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs), and [use_type_fn_nested_foreign.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs) cover cross-spec and nested-foreign equality (the last combining a nested-foreign import with `#[extend_where]`); [use_type_fn_deep_foreign.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_deep_foreign.rs) pins a three-hop foreign chain front-to-back, and [use_type_fn_reverse_order.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_reverse_order.rs) writes the same chain back-to-front and asserts a value flows through it at runtime — together they exercise the transitive context grounding and pin its order-independence. -- [use_type_generic_param.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs) covers an alias that collides with a generic parameter, [use_type_path_qualified.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_path_qualified.rs) the path-qualified trait form the `.` separator enables, and [implicit_arguments/cgp_fn_multi_and_use_type.rs](../../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs) the generic-argument trait form (`HasFooType.Foo`). - -The rejection cases are pinned in [parser_rejections/use_type.rs](../../../../crates/tests/cgp-macro-tests/tests/parser_rejections/use_type.rs): a `=` equality on a component, and a duplicate identifier or alias across specs, within one braced list, and on a component. - -Four `cargo-cgp` UI fixtures pin the post-codegen failures the attribute defers to the compiler (`cargo-cgp` files each by the quality of the output it renders, so the cyclic-context case sits under `usability/`): - -- [`acceptable/use-type/use_type_foreign_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-type/use_type_foreign_unsatisfied.rs) (a foreign `in Types` bound *enforced* on the trait rather than dropped) and [`acceptable/use-type/use_type_nested_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-type/use_type_nested_unsatisfied.rs) (the same at a nested two-hop depth) are both the [check-trait-failure](../../../errors/checks/check-trait-failure.md) class. -- [`acceptable/lowering/use_type_unknown_assoc.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/lowering/use_type_unknown_assoc.rs) (a misnamed imported associated type lowered into an unresolvable path, whose caret confirms the substitution preserves the user's identifier span) is the [unresolved-imported-type](../../../errors/lowering/unresolved-imported-type.md) class. -- [`usability/lowering/use_type_cyclic_context.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/lowering/use_type_cyclic_context.rs) (two `in Context` clauses that reference each other, so grounding never resolves either context and leaves the bare aliases in type position) surfaces as `E0425` "cannot find type" with the caret on the unresolved `in` alias — the sibling of the unresolved-imported-type class, but on the *context* rather than the associated-type name. - -## Source - -- The `use_type/` submodule in [cgp-macro-core/src/types/attributes/use_type/](../../../../crates/macros/cgp-macro-core/src/types/attributes/use_type/): `attribute.rs` (`UseTypeAttribute` and its parser), `ident.rs` (`UseTypeIdent`), `attributes.rs` (`UseTypeAttributes`, `grounded_specs`, and the two `transform_item_*` entry points), and `type_predicates.rs` (`derive_use_type_predicates`, `forbid_duplicate_aliases`, and the equality-unification helpers). -- The substitution visitor is `SubstituteAbstractTypes` in [cgp-macro-core/src/visitors/substitute_abstract_type.rs](../../../../crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs). -- The hosts that drive it: [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md), [entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md), and [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). diff --git a/docs/implementation/asts/attributes/uses.md b/docs/implementation/asts/attributes/uses.md deleted file mode 100644 index 7df65fd0..00000000 --- a/docs/implementation/asts/attributes/uses.md +++ /dev/null @@ -1,34 +0,0 @@ -# `#[uses]` — the AST stack - -`#[uses(TraitA, TraitB)]` imports `Self` trait bounds onto a provider's generated impl, reading like a `use` statement that lists the capabilities the provider depends on. It is a modifier attribute collected by a host macro rather than a standalone macro; this page covers its AST type and what it injects, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/uses.md](../../../reference/attributes/uses.md). - -## `UsesAttributes` - -The attribute parses into `UsesAttributes`, which holds a single `Vec` — one bound per imported capability. Each entry is a full [`syn::TypeParamBound`](https://docs.rs/syn/latest/syn/enum.TypeParamBound.html), so the parser accepts any bound a `where` clause accepts, not only the idiomatic `Trait` form: an associated-type-equality binding (`HasErrorType`), a higher-ranked bound (`for<'a> Trait<'a>`), or a lifetime bound all parse. The plain `Trait` form is the one to prefer, and the equality form is better expressed through [`#[use_type]`](use_type.md) when the trait is an abstract-type component; the wider grammar is simply what a `TypeParamBound` permits. - -The type is deliberately thin — it carries the bounds and hands them back through its `ToTypeParamBounds` impl, whose `to_type_param_bounds` clones the imports into a `Punctuated`. The host is what appends them; `UsesAttributes` only holds and yields them. - -## What the hosts inject - -`#[uses]` is accepted on `#[cgp_impl]` and `#[cgp_fn]`, and both land the bounds on the generated impl's `where` clause — on `Self` — never on the consumer trait, which is what keeps the dependency hidden from callers. The two hosts store the parsed bounds slightly differently, but the effect is the same: - -- On **`#[cgp_impl]`**, the bounds are parsed into the `UsesAttributes` held by `CgpImplAttributes` (its `uses.imports` field). `ItemCgpImpl::lower` adds them to the provider impl's `where` clause as `Self`-keyed predicates, alongside the bounds contributed by `#[use_type]` and `#[use_provider]`. -- On **`#[cgp_fn]`**, the bounds are parsed straight into a `Vec` field on `FunctionAttributes` (a field that mirrors `#[extend]`'s), and `preprocess` pushes each as a `Self: Trait` predicate onto the impl only. - -The contrast with [`#[extend]`](extend.md) is the crux and worth holding in mind: `#[uses]` adds an impl-side `Self` bound that the trait interface does not expose, whereas `#[extend]` makes its bound a *supertrait* of the generated trait, visible to every caller. `#[uses]` is therefore the way to declare an impl-side dependency, and `#[extend]` the way to widen the interface. - -## Tests - -The behavioral tests exercise `#[uses]` on both hosts and alongside generics: - -- [impl_side_dependencies/fn_uses.rs](../../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs) pins the `#[cgp_fn]` form — a `Self` trait bound imported as an impl-side dependency. -- [impl_side_dependencies/impl_uses.rs](../../../../crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses.rs) pins the `#[cgp_impl]` form. -- [generic_components/fn_impl_generics.rs](../../../../crates/tests/cgp-tests/tests/generic_components/fn_impl_generics.rs) exercises it alongside generic parameters. -- [impl_side_dependencies/fn_uses_associated_type.rs](../../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses_associated_type.rs) pins the associated-type-equality bound (`HasErrorType`) that `#[uses]` also accepts, on the `#[cgp_fn]` form. -- [impl_side_dependencies/impl_uses_associated_type.rs](../../../../crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses_associated_type.rs) exercises the same equality bound end-to-end on the `#[cgp_impl]` form. - -## Source - -- `UsesAttributes` and its `ToTypeParamBounds` impl are in [cgp-macro-core/src/types/attributes/uses.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/uses.rs). -- The `#[cgp_impl]` collector is `CgpImplAttributes` in [cgp_impl_attributes.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs); the `#[cgp_fn]` collector is `FunctionAttributes` in [function.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs). -- The hosts that drive it: [entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md) and [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). diff --git a/docs/implementation/asts/blanket_trait.md b/docs/implementation/asts/blanket_trait.md deleted file mode 100644 index 3c108473..00000000 --- a/docs/implementation/asts/blanket_trait.md +++ /dev/null @@ -1,25 +0,0 @@ -# The `blanket_trait` AST stack - -The `blanket_trait` stack is a single type. `#[blanket_trait]` has no multi-stage pipeline and no bespoke argument type — the entry function parses the optional context identifier and a `syn::ItemTrait` directly — so all the codegen lives in `ItemBlanketTrait`, whose `to_items` emits the trait unchanged plus one generated blanket impl. This document covers that type; the [entrypoint document](../entrypoints/blanket_trait.md) covers the shape of the items it produces. - -## `ItemBlanketTrait` - -`ItemBlanketTrait` holds the context identifier and the parsed trait, and does everything in `to_items`, which returns the cloned input trait followed by the impl built by `to_item_impl`. Because the trait is emitted verbatim, its default bodies and supertraits survive into the output untouched; the derivation is entirely on the impl. - -`to_item_impl` walks the trait's items once to build the blanket impl. It first collects the associated-type identifiers and runs a `RemoveSelfPathVisitor` over the whole trait to rewrite `Self::` references to the bare parameter name, then processes each item by kind: - -- a **type** contributes a `type = ;` assignment (from the lifted parameter), a new impl generic parameter, and — if it declared bounds — a `: ` predicate collected before its default is stripped; -- a **method** contributes an impl method whose body is the trait's default block, erroring if the default is absent; -- a **const** contributes an impl constant whose expression is the trait's default, erroring if the default is absent; -- any other item is rejected as unsupported. - -It then assembles the impl generics — the trait's own generics, plus the context parameter, plus one parameter per associated type — and builds the `where` clause: a single `#context: ` predicate carrying the trait's supertraits as the hidden dependency, followed by the collected associated-type bounds. The impl targets the trait (with the trait's own type generics) for the context type. The [entrypoint document](../entrypoints/blanket_trait.md) shows the resulting shapes. - -## Tests - -- `ItemBlanketTrait` is exercised end-to-end by the expansion snapshots indexed in the [entrypoint document's Snapshots section](../entrypoints/blanket_trait.md); the missing-default-body error path has no dedicated `cgp-macro-tests` failure case yet. - -## Source - -- `ItemBlanketTrait` lives in [cgp-macro-core/src/types/blanket_trait.rs](../../../crates/macros/cgp-macro-core/src/types/blanket_trait.rs). -- The `Self::`-to-parameter rewriting is done by `RemoveSelfPathVisitor` in [cgp-macro-core/src/visitors/remove_self_path.rs](../../../crates/macros/cgp-macro-core/src/visitors/remove_self_path.rs). diff --git a/docs/implementation/asts/cgp_component.md b/docs/implementation/asts/cgp_component.md deleted file mode 100644 index 545aeb29..00000000 --- a/docs/implementation/asts/cgp_component.md +++ /dev/null @@ -1,38 +0,0 @@ -# The `cgp_component` AST stack - -The `cgp_component` stack is the sequence of AST types that `#[cgp_component]` parses into and transforms through: the argument types, then the three pipeline stages `ItemCgpComponent`, `PreprocessedCgpComponent`, and `EvaluatedCgpComponent`. Each stage is a plain struct holding what the next stage needs, and the data flows in one direction — args plus a `syn::ItemTrait` become `ItemCgpComponent`, which `preprocess`es, then `eval`s, then `to_items` renders to a `Vec`. The [entrypoint document](../entrypoints/cgp_component.md) covers what each stage produces; this document covers the types. - -## `CgpComponentRawArgs` and `CgpComponentArgs` - -The attribute argument is parsed in two steps so that parsing and defaulting stay separate. `CgpComponentRawArgs` captures exactly what the user wrote as three `Option`s; `CgpComponentArgs` is the same three fields resolved to concrete values. - -`CgpComponentRawArgs` accepts either a bare provider identifier or a comma-separated `key: value` list over the keys `name`, `context`, and `provider`, rejecting a duplicate or unknown key: - -```rust -#[cgp_component(AreaCalculator)] // bare form -#[cgp_component { provider: AreaCalculator, context: Cx }] // keyed form -``` - -`CgpComponentArgs` is produced from the raw form by a `TryFrom` that applies the defaults: `provider` is required, `context` defaults to `__Context__`, and `name` defaults to the provider identifier with a `Component` suffix. Its `Parse` impl just parses the raw form and runs that conversion, so the entry function can parse the attribute straight into the defaulted type. - -## `ItemCgpComponent` - -`ItemCgpComponent` is the raw input stage — the parsed args and trait before any CGP attributes are stripped. Its `preprocess` step splits the CGP modifier attributes off the trait and hands the cleaned trait, the args, and the parsed attributes to the next stage, so later stages see a plain `syn::ItemTrait` alongside a structured record of the attributes that modify the output. - -## `PreprocessedCgpComponent` - -`PreprocessedCgpComponent` owns the core derivation. It holds the args, the preprocessed trait, and the attributes, and its `eval` step derives the provider trait, the two blanket impls, and the component marker struct, packaging them into the final stage. The one structural point worth knowing is that the provider trait is built once and shared with its blanket impl, so the trait and the impl cannot disagree; the shapes they produce are described in the [entrypoint document](../entrypoints/cgp_component.md). - -## `EvaluatedCgpComponent` - -`EvaluatedCgpComponent` is the final stage — a bag of all the derived items plus the args and attributes needed to render the standard provider impls. Its `to_items` step emits the five core items in fixed order and then appends the provider impls: always the `UseContext` and `RedirectLookup` impls, plus one `UseDelegate` impl per `#[derive_delegate]` attribute and one prefix impl per `#[prefix]` attribute. - -## Tests - -- [cgp-macro-tests/tests/parser_rejections/cgp_component.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs) pins the argument/trait parser's rejection of a non-trait item. -- The stage transforms are exercised end-to-end by the expansion snapshots indexed in the [entrypoint document's Snapshots section](../entrypoints/cgp_component.md). - -## Source - -- The stack lives in [cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/): the argument types in `args/`, `ItemCgpComponent` in `item.rs`, `PreprocessedCgpComponent` in `preprocessed/`, and `EvaluatedCgpComponent` in `evaluated/`. -- The `self`/`Self` rewriting is done by the visitors in [cgp-macro-core/src/visitors/](../../../crates/macros/cgp-macro-core/src/visitors/). diff --git a/docs/implementation/asts/cgp_data.md b/docs/implementation/asts/cgp_data.md deleted file mode 100644 index 0c55eb74..00000000 --- a/docs/implementation/asts/cgp_data.md +++ /dev/null @@ -1,56 +0,0 @@ -# The `cgp_data` AST stack - -The `cgp_data` stack is the small family of AST types that every extensible-data derive parses into before the shared codegen takes over: `ItemCgpData`, `ItemCgpRecord`, `ItemCgpVariant`, and the field-tag types (`Symbol`, `Index`, `FieldName`, `HasFieldBound`) that name each field. Unlike the [`cgp_component` stack](cgp_component.md), there is no multi-stage `preprocess → eval → to_items` transform here: each of the eight data derives parses its input into one of these types and then calls a `to_*` method that composes free codegen helpers into a `Vec`. This document covers the types; the per-derive entrypoint documents ([`derive_has_field`](../entrypoints/derive_has_field.md), [`derive_has_fields`](../entrypoints/derive_has_fields.md), [`derive_cgp_data`](../entrypoints/derive_cgp_data.md), [`derive_build_field`](../entrypoints/derive_build_field.md), [`derive_extract_field`](../entrypoints/derive_extract_field.md), [`derive_from_variant`](../entrypoints/derive_from_variant.md)) cover what each generated item looks like. - -The organizing idea is that a *record* is a struct and a *variant* is an enum, and the two shapes drive disjoint codegen: a record produces field getters, the `HasFields` product, and the incremental builder; a variant produces the `HasFields` sum, the `FromVariant` constructors, and the incremental extractor. `ItemCgpRecord` and `ItemCgpVariant` are the two shape-specific types, `ItemCgpData` is the union that dispatches on shape, and the field-tag types are shared by both because every field — named struct field, tuple field, or enum variant — is addressed by the same kind of type-level tag. - -## `ItemCgpData` - -`ItemCgpData` is the shape-dispatching wrapper that backs `#[derive(CgpData)]`. It is an enum of `Record(ItemCgpRecord)` or `Variant(ItemCgpVariant)`, and its `Parse` impl parses a `syn::Item` and routes a `struct` to the record arm and an `enum` to the variant arm, rejecting anything else with "expect body to be either a struct or enum". Its only method, `to_items`, forwards to the wrapped `ItemCgpRecord::to_items` or `ItemCgpVariant::to_items`, so `CgpData` on a struct emits exactly what `CgpRecord` emits and `CgpData` on an enum exactly what `CgpVariant` emits. - -## `ItemCgpRecord` - -`ItemCgpRecord` wraps a `syn::ItemStruct` and owns all struct-shape codegen. It is a thin struct — just `item_struct` — that exposes one method per slice of the record output plus a `to_items` that concatenates them: - -- `to_has_field_impls` — the per-field `HasField`/`HasFieldMut` getters, via `derive_has_field_impls_from_struct`. This is what `#[derive(HasField)]` calls. -- `to_has_fields_impls` — the five representation impls (`HasFields`, `HasFieldsRef`, `FromFields`, `ToFields`, `ToFieldsRef`), via `derive_has_fields_impls_from_struct`. This is the struct path of `#[derive(HasFields)]`. -- `to_build_field_items` — the incremental builder: the `__Partial{Name}` struct and its trait impls. This is what `#[derive(BuildField)]` calls. -- `to_items` — the full `#[derive(CgpRecord)]`/`#[derive(CgpData)]`-on-struct output: the getters, then the representation impls, then the builder items, in that order. - -The builder method names the partial companion struct `__Partial{ContextName}` and composes the helpers in the [`derive_builder/`](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/) submodule in a fixed order (builder struct, `HasBuilder`, `IntoBuilder`, `PartialData`, `FinalizeBuild`, then the per-field `UpdateField` and `HasField` impls). `#[derive(HasField)]`, `#[derive(HasFields)]`, `#[derive(BuildField)]`, `#[derive(CgpRecord)]`, and the struct path of `#[derive(CgpData)]` all construct an `ItemCgpRecord` and call one of these methods, which is why they never disagree about the shape they emit. - -## `ItemCgpVariant` - -`ItemCgpVariant` wraps a `syn::ItemEnum` and owns all enum-shape codegen, mirroring `ItemCgpRecord`: - -- `to_has_fields_impls` — the five representation impls over a *sum* rather than a product, via `derive_has_fields_impls_from_enum`. This is the enum path of `#[derive(HasFields)]`. -- `to_from_variant_impls` — the per-variant `FromVariant` constructors, via `derive_from_variant_from_enum`. This is what `#[derive(FromVariant)]` calls. -- `to_extract_field_items` — the incremental extractor: the `__Partial{Name}` and `__PartialRef{Name}` enums and their trait impls. This is what `#[derive(ExtractField)]` calls. -- `to_items` — the full `#[derive(CgpVariant)]`/`#[derive(CgpData)]`-on-enum output: the representation impls, then the `FromVariant` constructors, then the extractor items. - -The extractor method names two partial companion enums, `__Partial{ContextName}` (owned) and `__PartialRef{ContextName}` (borrowed), and drives most extractor helpers twice — once for each — passing a `bool` that selects the borrowed form. That is why the borrowed extractor mirrors the owned one and both stay in sync with the owned/ref pair. `#[derive(HasFields)]` on an enum, `#[derive(FromVariant)]`, `#[derive(ExtractField)]`, `#[derive(CgpVariant)]`, and the enum path of `#[derive(CgpData)]` all construct an `ItemCgpVariant` and call one of these methods. - -## `Symbol`, `Index`, and `FieldName` - -The field-tag types decide how a field is named at the type level, and every data derive routes its field identifiers through them. A named field or an enum variant is keyed by a `Symbol` — a type-level string — and a tuple-struct field is keyed by an `Index` — a type-level natural number of its position. - -`Symbol` holds the field's identifier as a `String` and, on `ToTokens`, emits the full type-level spelling `Symbol>` where `N` is the UTF-8 *byte* length (`str::len()`, so a multi-byte name records more than one node per character) and the `Chars` cons-list carries the characters. So the field name `foo` expands to `Symbol<3, Chars<'f', Chars<'o', Chars<'o', Nil>>>>`; the leading length works around the absence of const generics over strings. `Symbol::from_ident` calls `Ident::unraw` before recording the identifier, so a raw-identifier field such as `r#type` is tagged by its logical name `Symbol!("type")` rather than the literal `r#type` — matching what the [`Symbol!`](../../reference/macros/symbol.md) macro produces for the same name. `Index` holds a `usize` position and emits `Index`. `FieldName` is the enum that unifies the two — `Ident(Symbol)` or `Index(Index)` — so a helper that walks a struct's fields does not care whether they are named or positional; it converts each `Member` to a `FieldName` and lets `ToTokens` pick the right spelling. - -```rust -// a named field a tuple field -Symbol<3, Chars<'f', Chars<'o', Chars<'o', Nil>>>> Index<0> -``` - -`HasFieldBound` is the small companion that renders a `HasField`/`HasFieldMut` bound (`HasField`), used where the codegen needs to write such a bound as a `where`-clause fragment rather than as a full impl. - -## Tests - -- The shape-dispatch rejections are pinned in `cgp-macro-tests`'s `parser_rejections` target: [derive_cgp_data.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/derive_cgp_data.rs) drives `ItemCgpData` and asserts it refuses a non-struct/non-enum item and a non-single-field variant, and [derive_from_variant.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/derive_from_variant.rs) covers the variant-shape rejection (raised by the `get_variant_type` helper) plus `CgpVariant`'s non-enum rejection. The record derives' non-struct rejection (raised at `syn::parse2` of `ItemStruct`) has no dedicated test and is exercised only implicitly. -- The stage transforms are exercised end-to-end by the expansion snapshots indexed in the entrypoint documents' Snapshots sections — the [`snapshot_derive_has_field`](../entrypoints/derive_has_field.md#snapshots), [`snapshot_derive_has_fields`](../entrypoints/derive_has_fields.md#snapshots), and [`snapshot_derive_cgp_data`](../entrypoints/derive_cgp_data.md#snapshots) families. - -## Source - -- The stack lives in [crates/macros/cgp-macro-core/src/types/cgp_data/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/): `ItemCgpData` in `item.rs`, `ItemCgpRecord` in `record.rs`, and `ItemCgpVariant` in `variant.rs`. -- The record codegen helpers are under `derive_has_field.rs`, `derive_has_fields/`, and `derive_builder/`; the variant codegen helpers under `derive_has_fields/`, `derive_from_variant.rs`, and `derive_extractor/`. -- The field-tag types are in [crates/macros/cgp-macro-core/src/types/field/](../../../crates/macros/cgp-macro-core/src/types/field/): `Symbol` in `symbol.rs`, `Index` in `index.rs`, `FieldName` in `field_name.rs`, and `HasFieldBound` in `has_field_bound.rs`. -- The runtime traits these impls satisfy are defined in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/). diff --git a/docs/implementation/asts/cgp_fn.md b/docs/implementation/asts/cgp_fn.md deleted file mode 100644 index 774a0bb2..00000000 --- a/docs/implementation/asts/cgp_fn.md +++ /dev/null @@ -1,41 +0,0 @@ -# The `cgp_fn` AST stack - -The `cgp_fn` stack is the short sequence of AST types that `#[cgp_fn]` parses into and transforms through: the raw `ItemCgpFn` and the `PreprocessedItemCgpFn` it normalizes into. Data flows in one direction — an optional trait-name `Ident` plus a `syn::ItemFn` become `ItemCgpFn`, which `preprocess`es into `PreprocessedItemCgpFn`, whose `to_items` renders the trait and blanket impl to a `Vec`. The [entrypoint document](../entrypoints/cgp_fn.md) covers what each stage produces; this document covers the types and the implicit-argument helpers they lean on. - -## `ItemCgpFn` - -`ItemCgpFn` is the raw input stage — the parsed attribute identifier and function before any normalization. Its only field beyond the function is the optional trait-name `Ident`, left `None` when the user wrote `#[cgp_fn]` with no argument. - -Its `preprocess` step does all the up-front work that the emit stage assumes is already done. It resolves the trait name (the attribute identifier, or the function name run through `to_camel_case_str` to PascalCase), moves the function's visibility aside so the trait can carry it, extracts the `#[implicit]` arguments and prepends their field-reading `let` bindings to the body, parses the companion attributes into a `FunctionAttributes` record, and takes the function's generics out into a separate field. Everything it produces is packaged into `PreprocessedItemCgpFn`. - -## `PreprocessedItemCgpFn` - -`PreprocessedItemCgpFn` owns the emit stage. It holds the resolved trait name, the normalized `ItemFn` (implicit arguments already removed, body bindings already prepended), the parsed `ImplicitArgFields`, the `FunctionAttributes`, the saved visibility, and the saved generics. Its `to_items` produces the two output items by calling `to_item_trait` and `to_item_impl`. - -`to_item_trait` builds the trait: it wraps the function's signature as a `TraitItemFn` with no body, applies the saved generics (dropping the `where` clause, which is impl-side only), extends the supertraits with the `#[extend(...)]` bounds, adds any `#[extend_where(...)]` predicates to the trait's own `where` clause, runs the `#[use_type]` transform, re-attaches the raw attributes, and sets the saved visibility. `to_item_impl` builds the blanket impl: it emits `impl #ident #type_generics for __Context__` with the full function body, inserts `__Context__` as the leading generic parameter, appends any `#[impl_generics(...)]` parameters, then layers the `where` clause — first the `#[uses]`/`#[extend]` bounds as a `Self: …` predicate, then the `#[extend_where]` predicates, then the implicit `HasField` bounds last, and finally the `#[use_type]`/`#[use_provider]` transforms. - -The ordering here is the contract the snapshots pin: the implicit-argument bounds are always appended after the attribute-contributed predicates, so a reader of a generated impl sees user-declared dependencies before field requirements. - -## Implicit arguments: `ImplicitArgFields` and `ImplicitArgField` - -The implicit-argument types are shared building blocks that `#[cgp_fn]` and `#[cgp_impl]` both use, so they live under `types/implicits/` rather than in the `cgp_fn` module. An `ImplicitArgField` records one extracted argument — its field name, the field type to require, the field mutability (set from the argument's own type: `Some` only for a `&mut T` argument), the field mode (the conversion to apply), and the original argument type — and `ImplicitArgFields` is the collected list. - -The extraction is driven by `extract_and_parse_implicit_args`, which pulls every `#[implicit]`-marked argument out of a signature's inputs, parses each into an `ImplicitArgField`, and rejects the inputs extraction cannot lower (a missing `self` receiver, a `mut` pattern, a `&mut` implicit sharing the function with any other implicit, and a malformed `#[implicit(...)]`/`#[implicit = ...]` attribute that is not the bare marker form). `ImplicitArgField` then contributes in two directions: `to_has_field_bound` produces the `HasField`/`HasFieldMut` bound the impl requires, and `to_statement` produces the `let #name: #arg_type = self.get_field(...) ;` binding that `prepend_to_block` splices onto the front of the body. The conversion is chosen by `parse_field_type`, the same field-mode logic the getter macros use; the receiver's mutability enters only to validate that a `&mut T` argument is paired with a `&mut self` receiver. - -```rust -// for `#[implicit] name: &str` on `&self`, to_statement produces: -let name: &str = self.get_field(PhantomData::).as_str(); -``` - -`ImplicitArgFields` also carries `extract_from_impl_items`, used by `#[cgp_impl]` to collect implicit arguments across all methods of a provider impl and deduplicate them; `#[cgp_fn]` uses only the single-function `extract_and_parse_implicit_args` path. - -## Tests - -- The stage transforms are exercised end-to-end by the expansion snapshots indexed in the [entrypoint document's Snapshots section](../entrypoints/cgp_fn.md). -- The rejections enforced during implicit-argument extraction — an implicit argument without a `self` receiver, a `mut` binding pattern on an implicit argument, a `&mut` implicit combined with any other implicit, and a malformed `#[implicit]` attribute carrying arguments — are pinned by [parser_rejections/cgp_fn.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_fn.rs). - -## Source - -- The stack lives in [cgp-macro-core/src/types/cgp_fn/](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/): `ItemCgpFn` and its `preprocess` in `item.rs`, `PreprocessedItemCgpFn` and its `to_item_trait`/`to_item_impl` in `preprocessed.rs`. -- The implicit-argument types are in [cgp-macro-core/src/types/implicits/](../../../crates/macros/cgp-macro-core/src/types/implicits/) and their extraction in [cgp-macro-core/src/functions/implicits/](../../../crates/macros/cgp-macro-core/src/functions/implicits/); the field-mode conversion (`parse_field_type`) is in [cgp-macro-core/src/functions/field/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/field/parse.rs), shared with the getter stack in [asts/cgp_getter.md](cgp_getter.md). -- Companion-attribute parsing is in [cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs). diff --git a/docs/implementation/asts/cgp_getter.md b/docs/implementation/asts/cgp_getter.md deleted file mode 100644 index d2fadf78..00000000 --- a/docs/implementation/asts/cgp_getter.md +++ /dev/null @@ -1,40 +0,0 @@ -# The `cgp_getter` and `cgp_auto_getter` AST stack - -This stack covers the two getter macros together, because they share the getter-field parsing and the field-mode conversions and differ only in what they emit around a common getter-method body. `#[cgp_auto_getter]` parses a trait into `ItemCgpAutoGetter` and emits one blanket impl; `#[cgp_getter]` reuses the whole `#[cgp_component]` pipeline to produce an `EvaluatedCgpComponent`, wraps it in `ItemCgpGetter`, and appends three field-reading provider impls. Both feed on the shared `GetterField` parser and the `getter/` field-mode types. The two [entrypoint documents](../entrypoints/cgp_getter.md) cover what each stage produces; this document covers the types. - -## Shared getter-field parsing: `GetterField` and `parse_getter_fields` - -Both macros turn a getter trait's methods into `GetterField`s through the shared `parse_getter_fields` helper, which is the single source of truth for what a getter signature means. A `GetterField` records the field name (the method name), the field type to require, the return type, the receiver mutability, an optional `PhantomData` phantom-argument type, the field mode, and the receiver mode. - -The parser enforces the getter-method contract and resolves the two shorthands a reader most needs to know. A getter method must be a plain (non-const, non-async, non-unsafe, non-generic) method whose first argument is a reference — either `&self` (`ReceiverMode::SelfReceiver`) or a typed `&SomeType` receiver (`ReceiverMode::Type`), the latter letting a getter read a field out of a type other than the context, with `Self` rewritten to the context. The return type then determines the field type, the *field mode* — the conversion the getter body applies — and whether the read borrows the field mutably, all through the shared `parse_field_type`, which returns the field type, the `FieldMode`, and an `Option` for the access mutability: a `&str` return reads a `String` field (`FieldMode::Str`, `.as_str()`), an `Option<&T>` reads `Option` (`FieldMode::OptionRef`, `.as_ref()`), an `Option<&str>` reads `Option` (`FieldMode::OptionStr`, `.as_deref()`), a `&[T]` reads an `AsRef<[T]>` field (`FieldMode::Slice`, `.as_ref()`), an `MRef<'_, T>` wraps the borrow (`FieldMode::MRef`, `MRef::Ref(...)`), a plain `&T` is a bare `FieldMode::Reference`, and any other owned return — a path type, a tuple, or an array — is `FieldMode::Copy` (`.clone()`). A `&mut` in the type — the outer reference of `&mut T`/`&mut [T]` or the inner reference of `Option<&mut T>` — sets the access mutability, reusing the same `FieldMode` but selecting the `HasFieldMut`/`get_field_mut` read and the mutable conversion (`.as_mut()`, `.as_deref_mut()`, or an `AsMut<[T]>` bound for a mutable slice); a mutable read requires a `&mut self` receiver. Because the mutability is keyed off the type's own reference rather than the receiver, the same helper serves the `#[implicit]` case where an argument's mutability is independent of the receiver — a getter discards the returned mutability and keys off its receiver instead. `parse_getter_fields` also extracts an optional single associated return type and checks that, when present, the trait has exactly one method whose return type matches it. - -The getter-method body itself is built by `derive_getter_method` from the `types/getter/` module, which emits `receiver.get_field(PhantomData::) ` for the field's mode. The conversion suffix is chosen by `FieldMode::apply`, the single function that maps a field mode and mutability to the trailing `.as_str()`/`.as_ref()`/`MRef::Ref(...)`/etc.; the `#[implicit]` bindings in the [`cgp_fn` stack](cgp_fn.md) reach the same function through `GetFieldWithModeExpr`, which is why the two families convert fields identically. - -## `ItemCgpAutoGetter` - -`ItemCgpAutoGetter` is the whole AST for `#[cgp_auto_getter]` — a single struct holding the cleaned trait. Its `preprocess` associated function strips the CGP modifier attributes off the trait (discarding them, since the auto getter has no component to configure) and keeps the trait; there is no multi-stage pipeline because the macro emits no component. - -Its `to_items` emits the trait unchanged plus one blanket impl, built by `to_blanket_impl` → `derive_blanket_impl`. That impl fixes the context type to `__Context__`, adds each getter method reading its like-named field, and requires the corresponding `HasField` bound; a trait supertrait becomes a `__Context__: Supertrait` predicate, a trait generic parameter is preserved onto the impl, and a single associated return type is added as an extra parameter set to itself with its bounds carried over. The shape of the emitted impl is shown in the [entrypoint document](../entrypoints/cgp_auto_getter.md). - -## `EvaluatedCgpComponent` (reused) and `ItemCgpGetter` - -`#[cgp_getter]` produces no getter-specific parse stage of its own; it drives the `#[cgp_component]` pipeline to an `EvaluatedCgpComponent` (documented in the [`cgp_component` AST stack](cgp_component.md)) and then wraps that in `ItemCgpGetter`. The `TryFrom` conversion is where the getter fields are parsed: it runs `parse_getter_fields` over the consumer trait and stores the resulting `GetterField`s and optional associated type alongside the evaluated component. - -`ItemCgpGetter`'s `to_items` emits the component's own items first — the five core items plus the standard `UseContext`/`RedirectLookup` provider impls — and then appends the three getter-specific provider impls, each carrying its own `IsProviderFor` impl: - -- `to_use_fields_impl` builds the `UseFields` impl, keyed by method name: for each field it emits the getter-method body reading `Symbol!("field_name")` and requires the matching `HasField` bound on the receiver type. Always emitted. -- `to_use_field_impl` builds the `UseField<__Tag__>` impl, where `__Tag__` is a *free* generic parameter added to the impl generics, so the getter reads whatever field the wiring supplies. Emitted only for a single-getter trait. -- `to_with_provider_impl` builds the `WithProvider<__Provider__>` impl, which delegates field access to an inner `FieldGetter`/`MutFieldGetter` provider (a slice field uses an `AsRef<[T]>`-valued bound, or `AsMut<[T]>` for a mutable slice). Emitted only for a single-getter trait. - -Each of these threads the optional associated type through as an extra generic parameter, exactly as the auto-getter blanket impl does, keeping the three impls consistent with the consumer trait. - -## Tests - -- The stage transforms are exercised end-to-end by the expansion snapshots indexed in the two entrypoint documents' Snapshots sections — the [`#[cgp_getter]` snapshots](../entrypoints/cgp_getter.md) and the [`#[cgp_auto_getter]` snapshots](../entrypoints/cgp_auto_getter.md). -- [parser_rejections/getters.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/getters.rs) pins the getter-method contract checks in `parse_getter_fields`, driven through the shared `cgp_auto_getter` entrypoint: it rejects const, async, unsafe, and generic getter methods, a by-value `self` receiver, a `&mut` return under a `&self` receiver, more than one associated type, an associated type alongside a second method, and a non-getter trait item, plus `#[cgp_auto_getter]`'s rejection of any attribute argument. - -## Source - -- The auto-getter stack lives in [cgp-macro-core/src/types/cgp_auto_getter/](../../../crates/macros/cgp-macro-core/src/types/cgp_auto_getter/) (`item.rs`, `blanket.rs`), driven by [cgp-macro-lib/src/cgp_auto_getter.rs](../../../crates/macros/cgp-macro-lib/src/cgp_auto_getter.rs) and documented in [entrypoints/cgp_auto_getter.md](../entrypoints/cgp_auto_getter.md). -- The full-getter stack lives in [cgp-macro-core/src/types/cgp_getter/](../../../crates/macros/cgp-macro-core/src/types/cgp_getter/) (`item.rs`, `getter_field.rs`, `to_use_fields_impl.rs`, `use_field.rs`, `with_provider.rs`), driven by [cgp-macro-lib/src/cgp_getter.rs](../../../crates/macros/cgp-macro-lib/src/cgp_getter.rs) and documented in [entrypoints/cgp_getter.md](../entrypoints/cgp_getter.md); it reuses the [`cgp_component` stack](cgp_component.md). -- The shared getter-field parser is in [cgp-macro-core/src/functions/getter/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/getter/parse.rs), the field-mode conversion in [cgp-macro-core/src/functions/field/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/field/parse.rs), and the field-mode and getter-method emit types in [cgp-macro-core/src/types/getter/](../../../crates/macros/cgp-macro-core/src/types/getter/). diff --git a/docs/implementation/asts/cgp_impl.md b/docs/implementation/asts/cgp_impl.md deleted file mode 100644 index a55f44c8..00000000 --- a/docs/implementation/asts/cgp_impl.md +++ /dev/null @@ -1,65 +0,0 @@ -# The `cgp_impl` AST stack - -The `cgp_impl` stack is the sequence of AST types that `#[cgp_impl]` parses into and lowers through: the argument type `ImplArgs`, then the two lowering stages `ItemCgpImpl` and `LoweredCgpImpl`, then the `CgpProviderOrBareImpl` output that either forwards to the [`cgp_provider` stack](cgp_provider.md) or emits an untouched consumer impl. The data flows one way — args plus a `syn::ItemImpl` become `ItemCgpImpl`, which `lower`s into `LoweredCgpImpl`, which `lower`s again into `CgpProviderOrBareImpl`. The [entrypoint document](../entrypoints/cgp_impl.md) covers what each stage produces; this document covers the types. The `self`/`Self` rewrite that these stages depend on is done by the [`replace_self` visitors](#the-replace_self-visitors) described at the end. - -## `ImplArgs` - -`ImplArgs` is the parsed attribute argument: an optional `new` keyword, the provider type, and an optional `: ComponentType` override. Its `Parse` impl reads the keyword, then the provider type, then a component type if a colon follows: - -```rust -pub struct ImplArgs { - pub new: Option>, - pub provider_type: Type, - pub component_type: Option, -} -``` - -When the macro lowers to the provider stage, these fields are copied straight into a [`ProviderArgs`](cgp_provider.md#providerargs) (the `new` and `component_type` fields), and the `provider_type` becomes the `Self` type of the generated provider impl. - -## `ItemCgpImpl` - -`ItemCgpImpl` is the raw input stage — the parsed args and the `impl` block before any lowering. Its `lower` step does the attribute processing and header normalization: it parses the CGP modifier attributes off the block into a `CgpImplAttributes`, extracts `#[implicit]` parameters into `HasField` bounds on `Self`, folds `#[uses]`/`#[use_type]`/`#[use_provider]` into the impl generics and bodies, builds a delegation impl for each `#[default_impl]`, and resolves the provider trait path and context type. - -The header handling is the one structural point worth knowing. When the block has a `for` clause, the trait path is the provider trait and the block's `Self` type is the context. When the `for` clause is omitted, the block's `Self` type *is* the provider trait path, and `lower` inserts the reserved `__Context__` as the leading impl generic so the next stage always has an explicit context to move into position: - -```rust -// input, no `for` // recorded on LoweredCgpImpl -impl AreaCalculator { provider_trait_path = AreaCalculator - fn area(&self) -> f64 context_type = __Context__ - { … } item_impl generics gain a leading __Context__ -} -``` - -`lower` hands the cleaned `item_impl`, the args, the `context_type`, the `provider_trait_path`, and the `default_impls` to `LoweredCgpImpl`. - -## `LoweredCgpImpl` - -`LoweredCgpImpl` owns the consumer-to-provider rewrite. Its `lower` step branches on the provider type. For the ordinary case it calls `to_raw_item_impl` to produce a provider-trait impl and wraps it in an [`ItemCgpProvider`](cgp_provider.md#itemcgpprovider), reusing that stack's `IsProviderFor` derivation and struct emission. For the `#[cgp_impl(Self)]` case — where the provider type is the literal `Self` — it returns the original block unchanged as a bare consumer impl, requiring a `for` clause and erroring otherwise. - -`to_raw_item_impl` is where the rewrite happens: it swaps the block's `Self` type to the provider type, inserts the context type as the provider trait's leading type argument, computes the receiver identifier (the context type snake-cased and double-underscored, or the literal `__context__` when the context is not a plain identifier), and runs the three `replace_self` visitors. It first collects the block's own associated types so the type visitor skips rewriting `Self::Output` and other local associated types. - -## `CgpProviderOrBareImpl` - -`CgpProviderOrBareImpl` is the output of the second `lower` — an enum with a `Bare` variant holding the untouched consumer impl and a `Provider` variant holding a [`LoweredCgpProvider`](cgp_provider.md#loweredcgpprovider). Its `ToTokens` renders whichever variant it holds, so the `Bare` case emits a single consumer impl while the `Provider` case emits the provider impl, its `IsProviderFor` impl, and any provider struct. The entrypoint appends the `default_impls` after this. - -## The `replace_self` visitors - -Three `VisitMut` passes in [cgp-macro-core/src/visitors/replace_self/](../../../crates/macros/cgp-macro-core/src/visitors/replace_self/) perform the rewrite that turns consumer-style syntax into provider-style, and they run in this order: - -- **`ReplaceSelfTypeVisitor`** rewrites the `Self` type to the context type and `Self::Foo` paths to `Context::Foo`, but skips any path whose associated type is in the block's local associated-type list, so a trait's own `Self::Output` is left intact. It handles macro bodies at the token level too, since `VisitMut` does not see inside a `macro!( … )`. -- **`ReplaceSelfReceiverVisitor`** rewrites the method receiver into an explicit context parameter, preserving the reference and mutability shape: `&self` becomes `ctx: &Context`, `&mut self` becomes `ctx: &mut Context`, `self` becomes `ctx: Context`, and `mut self` becomes `mut ctx: Context` — the `mut` binds the parameter, so it precedes the identifier rather than the type. A lifetime on the receiver is carried onto the parameter type. -- **`ReplaceSelfValueVisitor`** rewrites every `self` *value* expression to the context identifier, again descending into macro bodies at the token level. Inside a macro body it rewrites only the value form: a `self::` module path is left intact, since a `self` immediately followed by `::` is the current module rather than the receiver. - -All three visitors override `visit_item_mut` to a no-op, so they **stop at any item nested inside a method body** — a local `struct`, `impl`, `trait`, or `fn` introduces its own `self`/`Self` scope that names that item, not the enclosing context, and must be left untouched. Because a trait's or impl's associated items are `TraitItem`/`ImplItem` rather than `Item`, this guard fires only for block-nested items; the block's own methods and associated types are still rewritten, and closures — which *do* capture the outer `self` — are still descended into. The token-level macro pass cannot see scope, so this stopping applies at the AST level only. - -The receiver identifier and the context type these visitors substitute are the ones `to_raw_item_impl` computed. The same visitors are also used by the [`cgp_component` stack](cgp_component.md) to lower a consumer trait's default method bodies into the provider trait, so the nested-item guard protects those bodies too; they are not used by the [`cgp_provider` stack](cgp_provider.md), which already receives provider-form input. - -## Tests - -- The stage transforms are exercised end-to-end by the `snapshot_cgp_impl!` expansion snapshots and the behavioral tests indexed in the [entrypoint document](../entrypoints/cgp_impl.md). - -## Source - -- The stack lives in [cgp-macro-core/src/types/cgp_impl/](../../../crates/macros/cgp-macro-core/src/types/cgp_impl/): `ImplArgs` in `args.rs`, `ItemCgpImpl` in `item.rs`, `LoweredCgpImpl` and `to_raw_item_impl` in `lowered.rs`, and `CgpProviderOrBareImpl` in `provider_or_bare.rs`. -- The companion-attribute parsing is in [cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs), and the `self`/`Self` rewriting in [cgp-macro-core/src/visitors/replace_self/](../../../crates/macros/cgp-macro-core/src/visitors/replace_self/). -- The provider stage this stack hands off to is documented in [asts/cgp_provider.md](cgp_provider.md). diff --git a/docs/implementation/asts/cgp_provider.md b/docs/implementation/asts/cgp_provider.md deleted file mode 100644 index 26d5a8b9..00000000 --- a/docs/implementation/asts/cgp_provider.md +++ /dev/null @@ -1,67 +0,0 @@ -# The `cgp_provider` AST stack - -The `cgp_provider` stack is the sequence of AST types that both [`#[cgp_provider]`](../entrypoints/cgp_provider.md) and [`#[cgp_new_provider]`](../entrypoints/cgp_new_provider.md) parse into and lower through — the two macros share this stack entirely and differ only in whether the `new` keyword is set. The argument type `ProviderArgs` and a `syn::ItemImpl` become an `ItemCgpProvider`, whose single `lower` step derives the `IsProviderFor` impl (via `ItemProviderImpl`) and the provider struct (via `EmptyStruct`), packaging all three items into a `LoweredCgpProvider` that renders them. The argument-splitting helper `ProviderImplArgs` supports the `IsProviderFor` derivation. The [`#[cgp_provider]` entrypoint](../entrypoints/cgp_provider.md) covers what the stage produces; this document covers the types. - -## `ProviderArgs` - -`ProviderArgs` is the parsed attribute argument, shared by both macros: an optional `new` flag and an optional component-type override. - -```rust -pub struct ProviderArgs { - pub new: Option>, - pub component_type: Option, -} -``` - -The parser reads only the component type; it never parses the `new` flag from the attribute, so both macros' argument grammar is a bare `ComponentType?`. The `new` flag is set programmatically instead: `#[cgp_provider]` leaves it `None`, `#[cgp_new_provider]` forces it to `Some` after parsing, and when [`#[cgp_impl]`](cgp_impl.md#implargs) lowers to this stack it constructs a `ProviderArgs` from its own `ImplArgs`, copying the `new` and `component_type` fields. Keeping `new` out of the parser means a stray `#[cgp_provider(new Name)]` is not silently treated as a struct declaration — `Name` is left as an unexpected trailing token and rejected. - -## `ItemCgpProvider` - -`ItemCgpProvider` is the input stage — the args and the provider-trait impl. Its `lower` step drives the whole macro in one pass, delegating to three helpers on itself: - -- `component_type` derives the component: it reads the provider trait's identifier and appends `Component` (so `AreaCalculator` → `AreaCalculatorComponent`), unless the attribute supplied an explicit override. -- `ItemProviderImpl::to_is_provider_for_impl` derives the `IsProviderFor` impl. -- `to_provider_struct` derives the provider struct, returning `None` when `new` is unset. - -It packages the original `item_impl` (emitted verbatim), the derived `IsProviderFor` impl, and the optional struct into a `LoweredCgpProvider`. - -## `ItemProviderImpl` and the `IsProviderFor` derivation - -`ItemProviderImpl` pairs a component type with a provider impl and derives the `IsProviderFor` marker impl from it. `to_is_provider_for_impl` clones the provider impl, clears its body, associated types, attributes, `defaultness`, and `unsafety`, and swaps the trait for `IsProviderFor` — keeping the original generic parameters, `where` clause, and the provider impl's own `for` token so the marker holds under exactly the same conditions and none of its structural tokens fall back to the macro `call_site` span (the cloned `impl` keyword, generics, and self type already carry the user's spans; reusing the `for` token keeps the middle of the header from leaking too): - -```rust -// from impl ComputerRef for FirstNameToString where … -// to impl IsProviderFor -// for FirstNameToString where … {} -``` - -The derived **component reference** — the `Component` in that `IsProviderFor` — is spanned on `call_site`, not on the provider trait it is derived from. It appears only as this interior type argument, which anchors no error caret (a coherence conflict on the marker reports on the `impl` header, an unmet dependency on the `where`-clause bound), so a narrower span buys nothing at the compiler. It would, though, mislead the editor: rust-analyzer maps a source token to its expansion by source range, so had this reference borrowed the provider trait's span it would share that trait token's range, and go-to-definition on the provider trait a user wrote (in a `#[cgp_impl]` block or a hand-written provider impl) would then offer the component struct as a spurious second target. `call_site` shares no narrow user token's range, keeping the reference out of the editor's way. This is the reference-side dual of the [`#[cgp_component]` marker struct](../entrypoints/cgp_component.md#behavior-and-corner-cases), whose *definition* is instead spanned on the provider identifier so navigation to it lands cleanly; see the [Spans note](../README.md#spans-aim-generated-items-at-the-token-the-user-wrote). - -The trait arguments come from `ProviderImplArgs::from_generic_args`, and the derivation ends by running [`replace_provider_in_generics`](../../../crates/macros/cgp-macro-core/src/visitors/replace_provider.rs) with a map from the provider identifier to the component type, which rewrites a `Provider: SomeTrait` `where`-bound into an `IsProviderFor<…>` bound so a higher-order provider's inner-provider dependency shows up as an `IsProviderFor` obligation. - -## `ProviderImplArgs` - -`ProviderImplArgs` splits a provider trait's generic arguments into the context type and the `Params` tuple. Walking the arguments in order, it takes the first *type* argument as the context and collects the rest as `Params`; a lifetime always goes into `Params` (its `ToTokens` lifts it to `Life<'a>`) regardless of position, and a `const` argument is rejected with a spanned error. A trait path with no type argument at all is an error, since there is no context to place in the leading position. - -## `LoweredCgpProvider` - -`LoweredCgpProvider` is the output stage — a bag of the three emitted items. Its `ToTokens` renders them in order: the provider impl, the `IsProviderFor` impl, then the provider struct (which renders to nothing when `None`). - -## `EmptyStruct` - -`EmptyStruct` is the provider struct, emitted only when `new` is set. `to_provider_struct` reads the shape from the impl's `Self` type: a plain name yields a unit `pub struct Name;`, while a generic provider yields a struct whose single `PhantomData` field binds every parameter — a lifetime parameter is bound as `Life<'a>` so the struct stays covariant and `'static`-friendly. Two or more parameters are grouped into a `PhantomData` tuple; a lone parameter is bound directly (`PhantomData`, not `PhantomData<(InCode)>`) to avoid a single-element parenthesized type, and a const-only provider gets `PhantomData<()>`. - -```rust -// generic provider Self type SpawnAndRun -pub struct SpawnAndRun(pub ::core::marker::PhantomData); -``` - -## Tests - -- The stage transforms are exercised by the `snapshot_cgp_provider!` snapshots and the behavioral tests indexed in the [`#[cgp_provider]` entrypoint document](../entrypoints/cgp_provider.md); `#[cgp_new_provider]`'s direct coverage is indexed in [its entrypoint document](../entrypoints/cgp_new_provider.md). - -## Source - -- The stack lives in [cgp-macro-core/src/types/cgp_provider/](../../../crates/macros/cgp-macro-core/src/types/cgp_provider/): `ProviderArgs` in `args.rs`, `ItemCgpProvider` and its helpers in `item.rs`, `LoweredCgpProvider` in `lower.rs`, and `ProviderImplArgs` in `provider_impl_args.rs`. -- The `IsProviderFor` derivation (`ItemProviderImpl`) is in [cgp-macro-core/src/types/provider_impl.rs](../../../crates/macros/cgp-macro-core/src/types/provider_impl.rs), the provider struct (`EmptyStruct`) in [cgp-macro-core/src/types/empty_struct.rs](../../../crates/macros/cgp-macro-core/src/types/empty_struct.rs), and the provider-name rewrite in [cgp-macro-core/src/visitors/replace_provider.rs](../../../crates/macros/cgp-macro-core/src/visitors/replace_provider.rs). -- The consumer-style stack that hands off to this one is documented in [asts/cgp_impl.md](cgp_impl.md). diff --git a/docs/implementation/asts/cgp_type.md b/docs/implementation/asts/cgp_type.md deleted file mode 100644 index 5a0804f9..00000000 --- a/docs/implementation/asts/cgp_type.md +++ /dev/null @@ -1,23 +0,0 @@ -# The `cgp_type` AST stack - -The `cgp_type` stack is thin: `#[cgp_type]` reuses the whole [`cgp_component` AST stack](cgp_component.md) to derive the component and adds a single wrapper type, `ItemCgpType`, that carries the finished `EvaluatedCgpComponent` and appends the two abstract-type provider impls. There is no bespoke argument type — the attribute is parsed as `CgpComponentArgs`, with the provider-name default patched in the [entrypoint function](../entrypoints/cgp_type.md) before the pipeline runs. This document covers `ItemCgpType` and the `ItemProviderImpl`/`ItemProviderImpls` helpers it renders through; the [entrypoint document](../entrypoints/cgp_type.md) covers what each item produces. - -## `ItemCgpType` - -`ItemCgpType` is the final rendering stage. It holds a single field, the `EvaluatedCgpComponent` produced by the shared `preprocess → eval` pipeline, and exists only to emit the extra impls on top of the standard component output. Its `to_items` first calls the wrapped component's own `to_items` (the five core items plus the `UseContext` and `RedirectLookup` provider impls) and then extends that vector with the abstract-type impls. - -The extra impls are built by `to_item_provider_impls`, which reads the component's args, provider trait, and single associated type (via `extract_item_type_from_trait`, which also validates that the trait body is exactly one non-generic associated type) and produces two `ItemProviderImpl`s: the `UseType` impl and the `WithProvider<__Provider__>` impl. It clones the provider trait's generics for each, inserts the associated-type name (and `__Provider__` for the `WithProvider` case) as leading impl parameters, and moves any associated-type bound onto both impls with `Self::` rewritten to the free parameter. The shapes of the two impls are shown in the [entrypoint document](../entrypoints/cgp_type.md). - -## `ItemProviderImpl` and `ItemProviderImpls` - -`ItemProviderImpl` pairs one provider `syn::ItemImpl` with the component-marker type it implements, and its role in this stack is to attach the matching `IsProviderFor` impl: `to_item_impls` emits the provider impl and a companion `IsProviderFor` impl carrying the same generics and `where` clause, so the two can never disagree. `ItemProviderImpls` is just the collection of them, and its `to_item_impls` flattens the pairs into the `Vec` that `ItemCgpType::to_items` folds into the output. This pairing helper is shared with other macros that emit provider impls, so `#[cgp_type]` gets the `IsProviderFor` bookkeeping for free. - -## Tests - -- The stage is exercised end-to-end by the expansion snapshots indexed in the [entrypoint document's Snapshots section](../entrypoints/cgp_type.md); the trait-shape rejection in `extract_item_type_from_trait` has no dedicated `cgp-macro-tests` failure case yet. - -## Source - -- `ItemCgpType` and `extract_item_type_from_trait` live in [cgp-macro-core/src/types/cgp_type/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_type/item.rs). -- The `ItemProviderImpl`/`ItemProviderImpls` helpers are in [cgp-macro-core/src/types/provider_impl.rs](../../../crates/macros/cgp-macro-core/src/types/provider_impl.rs). -- The associated-type-bound rewriting is done by [`get_bounds_and_replace_self_assoc_type`](../../../crates/macros/cgp-macro-core/src/visitors/self_assoc_type.rs), and the shared component types are in [cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/), documented in [asts/cgp_component.md](cgp_component.md). diff --git a/docs/implementation/asts/check_components.md b/docs/implementation/asts/check_components.md deleted file mode 100644 index 31d946ab..00000000 --- a/docs/implementation/asts/check_components.md +++ /dev/null @@ -1,58 +0,0 @@ -# The `check_components` and `delegate_and_check_components` AST stacks - -These two stacks generate the compile-time wiring checks. The `check_components` stack parses a check table into entries and lowers each into a check-trait impl; the `delegate_and_check_components` stack is a thin layer that reuses both this stack and the [`delegate_component` stack](delegate_component.md), deriving check entries from delegation keys. Evaluation flows from a `CheckComponentsTable` down to `EvaluatedCheckEntry` values, each rendering one empty impl of the table's check trait. The [`check_components!`](../entrypoints/check_components.md) and [`delegate_and_check_components!`](../entrypoints/delegate_and_check_components.md) entrypoint documents cover what the pipelines produce; this document covers the types. - -## `CheckComponentsTables` and `CheckComponentsTable` - -`CheckComponentsTables` is the whole `check_components!` body — a `Vec`, parsed by looping until the input is empty, so one invocation can carry several context blocks. `to_items` concatenates each table's items. - -`CheckComponentsTable` is one context block: an optional `check_providers` list, an optional leading generic list, the derived-or-overridden check-trait name, the context type, an optional `where` clause, and the `CheckEntries`. Its `Parse` reads the `#[check_trait]` and `#[check_providers]` attributes (rejecting any other, a repeat of either, and an empty `#[check_providers()]`), derives the trait name as `__Check{Context}` from the final segment of the context type's path when not overridden — so a path-qualified context is accepted — and parses the braced entries. Its `eval` builds the check trait once — supertraiting `CanUseComponent` normally, or `IsProviderFor<…, Context, …>` under `#[check_providers]` — then emits one impl per evaluated entry. For the context-checking form it overrides the impl's `Self`-type span with the component's span so an error points at the component; the `#[check_providers]` form instead emits one impl per listed provider. - -## `CheckEntries` and `CheckEntry` - -`CheckEntries` is the braced list, a `Punctuated`; its `eval` flattens every entry into `EvaluatedCheckEntry` values. - -`CheckEntry` is one line: a `CheckKey` and an optional `CheckValue` after a colon. Its `eval` produces the cartesian product of keys and values — one evaluated entry per (key, value) pair — and, when there is no value, one entry with unit params. It also chooses the diagnostic span per entry, preferring the component or the parameter side depending on which list is longer, so the error lands on the token the user is most likely to have gotten wrong. - -## A key: `CheckKey` - -`CheckKey` is the left side, an enum of a single `Type` or a bracketed `Multi` list. `to_keys` returns one type for the single form and one per element for the array form, which is how a bracketed key checks several components at once. - -## A value: `CheckValue` - -`CheckValue` is the optional right side — the generic parameters to check the component with — an enum of a single `TypeWithGenerics` or a bracketed `Multi` list. `to_values` returns one or many, so a bracketed value checks one component against several parameter sets. `TypeWithGenerics` is a type plus an optional leading generic list, letting a parameter introduce its own generics (` &'a T`) that merge with the table's when the impl is built. An omitted value defaults to the unit type. - -## `EvaluatedCheckEntry` - -`EvaluatedCheckEntry` is the lowered form: the component key, an optional `TypeWithGenerics` value, and the diagnostic span. It is a plain data struct; `CheckComponentsTable::eval` consumes it to build each impl, placing the key in the `__Component__` position and the value (or `()`) in the `__Params__` position. - -## `ItemDelegateAndCheckComponents` - -`ItemDelegateAndCheckComponents` is the entire `delegate_and_check_components!` body — just a wrapper around a parsed [`DelegateTable`](delegate_component.md). It adds the derivation from delegation to checking: - -- `check_trait_ident` derives the check-trait name as `__CanUse{Context}` (distinct from `check_components!`'s `__Check{Context}`) or reads a single `#[check_trait]` attribute off the table. -- `to_check_entries` walks the delegation keys via `ToKeysWithCheckParams` and turns each into `CheckEntry` values. -- `to_check_components` packages those entries, the table's generics, the derived name, and the context type into a `CheckComponentsTable`, so the checking half is rendered by the ordinary `check_components` pipeline. - -The wiring half is not re-derived here — the entrypoint calls `DelegateTable::eval` directly. - -## `CheckParamsAttribute` and `KeyWithCheckParams` - -`CheckParamsAttribute` is the per-entry check control parsed from the delegation key's attributes: `Default` (check with unit params), `Skip` (`#[skip_check]`, no check), or `Multi` (`#[check_params(...)]`, one check per listed parameter). Its `merge` combines a bracket-level attribute with an inner key's own — unioning two `Multi` sets, but erroring when `Skip` meets `Multi` — and `parse_attributes` enforces that at most one attribute appears and that `#[skip_check]` takes no arguments. - -`KeyWithCheckParams` pairs a delegation key type with its own generics and its resolved `CheckParamsAttribute`. Its `to_check_entries` turns the pairing into `CheckEntry` values: `Default` yields one bare entry, `Skip` yields none, and `Multi` yields one valued entry per parameter. When the key carries generics, they are threaded onto each check value's `TypeWithGenerics` so the derived impl binds them — a `Default` key gets a unit-params value carrying the generics (rather than the bare `value: None`), and each `Multi` parameter carries them too. - -## `ToKeysWithCheckParams` - -`ToKeysWithCheckParams` is the trait that walks a `DelegateEntries` and collects `KeyWithCheckParams`. It handles single and array keys (merging bracket-level and inner attributes), carrying each key's own generics into the `KeyWithCheckParams` so a generic key binds its parameters on the derived check impl. It deliberately produces no check entries for redirect (`=>`) mappings, `@`-path keys, and the `open`/`namespace`/`for` statement forms — validating that each carries no attribute rather than silently dropping one. - -## Tests - -- Both stacks are exercised by the expansion snapshots indexed in the [`check_components!`](../entrypoints/check_components.md) and [`delegate_and_check_components!`](../entrypoints/delegate_and_check_components.md) entrypoint documents; each is a compile-only test, so a successful build is the passing assertion. -- There are no `cgp-macro-tests` failure cases for the check family. - -## Source - -- The `check_components` stack lives in [cgp-macro-core/src/types/check_components/](../../../crates/macros/cgp-macro-core/src/types/check_components/): the tables in `tables.rs` and `table.rs` (with the trait build, attribute parsing, name derivation, supertrait choice, and span override in `table.rs`), the entries in `entries.rs` and `entry.rs`, the key and value in `key.rs` and `value.rs`, `TypeWithGenerics` in `type_with_generics.rs`, and `EvaluatedCheckEntry` in `evaluated_check_entry.rs`. -- The `delegate_and_check_components` stack lives in [cgp-macro-core/src/types/delegate_and_check_components/](../../../crates/macros/cgp-macro-core/src/types/delegate_and_check_components/): the wrapper item in `item.rs`, the attributes in `check_params.rs`, the per-key conversion in `key_with_check_params.rs`, and the entry walk in `to_keys_with_check_params.rs`. It reuses the [`DelegateTable`](delegate_component.md). -- All impls are built with [parse_internal!](../macros/parse_internal.md). diff --git a/docs/implementation/asts/delegate_component.md b/docs/implementation/asts/delegate_component.md deleted file mode 100644 index d9e5fb1f..00000000 --- a/docs/implementation/asts/delegate_component.md +++ /dev/null @@ -1,65 +0,0 @@ -# The `delegate_component` AST stack - -The `delegate_component` stack is the set of AST types that `delegate_components!` (and, for its wiring half, [`delegate_and_check_components!`](../entrypoints/delegate_and_check_components.md)) parses the macro body into and lowers to impls. The top is `DelegateTable`; below it the body is a `DelegateEntries` of leading statements plus comma-separated mappings, and each mapping pairs a key with a value under one of three operators. Evaluation flows in one direction: the tree lowers to a flat list of `EvaluatedDelegateEntry` values, each of which renders a `DelegateComponent` impl and an `IsProviderFor` impl. The [entrypoint document](../entrypoints/delegate_components.md) covers what the whole pipeline produces; this document covers the types, one per role in the grammar. - -## `DelegateTable` and `EvaluatedDelegateTable` - -`DelegateTable` is the whole parsed body: outer attributes, an optional leading generic list (`ImplGenerics`), an optional `new` keyword, the target type, and the braced `DelegateEntries`. Its `eval` produces an `EvaluatedDelegateTable` — a bag of `ItemImpl`s and `EmptyStruct`s — whose `ToTokens` emits the structs first, then the impls. - -`eval` does three things: if `new` is present it parses the target into an identifier-plus-generics and pushes an `EmptyStruct` for it; it builds the entry impls from `DelegateEntries::build_impls`; and it collects every nested `UseDelegate` inner table (via `ExtractInnerDelegateTables`) and emits each inner table's own struct and impls. The target type and the outer generics are threaded down into every entry so all impls carry the table's generics. - -## `DelegateEntries` - -`DelegateEntries` is the table body: a `Vec` followed by a `Punctuated`. Its `Parse` impl peeks for a statement keyword (`namespace`, `open`, or `for`) and consumes all leading statements first, then parses the remaining comma-separated mappings — which is why a statement written after a mapping fails to parse. Its `eval_entries` lowers the statements first, then the mappings, concatenating the evaluated entries; `build_impls` then renders each into its `DelegateComponent`/`IsProviderFor` pair. - -## `InnerDelegateTable` - -`InnerDelegateTable` is a nested table lifted out of a `UseDelegate` value: an identifier, its generics, and its own `DelegateEntries`. It builds its own `EmptyStruct` and, treating its own identifier-plus-generics as the target type, its own entry impls — so a nested table is evaluated exactly like a top-level one. `ExtractInnerDelegateTables` recurses so that nesting to any depth is flattened into the table's struct-and-impl list. - -## `DelegateStatement` - -`DelegateStatement` is the leading statement form, an enum over three variants that all lower through `eval_entries` into the same flat entry list: - -- **`OpenDelegateStatement`** (`open { A, B };`, or `open A;` for a single component) — opens each listed component for per-value wiring. Its parser peeks for a brace: a `{ … }` list parses one or more comma-separated components, while the braceless form parses exactly one component type, so opening several at once still requires the braces. For each component it emits an entry whose key is the component and whose value is `RedirectLookup>`, rooting a redirect at the component name in the context's own table. The `@Component.Key` mappings that follow store providers under the extended path. -- **`NamespaceDelegateStatement`** (`namespace SomeNamespace;`) — forwards every lookup through a namespace trait. It lowers via the shared "for-entry" path to a blanket `DelegateComponent<__Key__>` impl bounded on `__Key__: SomeNamespace`, so any key the namespace defines is inherited. -- **`ForDelegateStatement`** (`for in SomeTable where … { … }`) — a loop that pulls mappings out of another lookup table. Each inner mapping produces a for-entry whose namespace-trait bound is reconstructed with the table type and a `Delegate = value` binding appended to the namespace path's arguments. - -The namespace and `for` forms share `EvaluatedForEntry` and `eval_delegate_entries_via_for`, which build the `Namespace<…, Delegate = …>` bound and the `__Key__`/`__Value__` generics before producing the final `EvaluatedDelegateEntry`. `EvaluatedForEntry` carries its own diagnostic `span` (the namespace name, or a `for` mapping's key) since its `mapping_key` is a synthesized `__Key__`; it passes that span to the entry. - -## `DelegateMapping` - -`DelegateMapping` is one comma-separated entry, an enum whose variant is chosen by the operator after the key. `DelegateMode` peeks the operator and drives the split: - -- **`NormalDelegateMapping`** (`:`) — maps the key straight to the value type; the evaluated entry's `Delegate` is the value. -- **`DirectDelegateMapping`** (`->`) — forwards to the value's own entry for the key. It sets `Delegate` to `>::Delegate` and adds a `Value: DelegateComponent` bound to the entry's generics. -- **`RedirectDelegateMapping`** (`=>`) — redirects the lookup along an `@`-path value. It sets `Delegate` to `RedirectLookup`, using the path directly for a plain key or a wildcard-terminated prefix for a path key. - -Only Normal and Direct mappings can carry a nested inner table (their values are `DelegateValue`); a Redirect value is a bare path and contributes no inner table. - -## A key: `DelegateKey` - -`DelegateKey` is the left side of a mapping, an enum over three shapes selected by a lookahead on a fork (after skipping any attributes and generics): a leading `@` gives a `PathDelegateKey`, a leading bracket gives a `MultiDelegateKey`, otherwise a `SingleDelegateKey`. `EvalDelegateKey::eval` returns a `Vec` (a type, its generics, and a diagnostic `span`), so a single mapping can expand to several keyed impls. The `span` is the source token the key was written as, carried separately because a `@`-path key lowers to a synthesized `PathCons<..>` type whose own span points at the macro `call_site`; it flows into the entry and drives the [error-span re-spanning](../entrypoints/delegate_components.md#error-spans). - -- **`SingleDelegateKey`** — attributes, an optional generic list, and a type; evaluates to one key carrying its generics. -- **`MultiDelegateKey`** — a bracketed, comma-separated list of `SingleDelegateKey`; the array-key form, evaluating to one key per element so `[A, B]: P` becomes two impls. -- **`PathDelegateKey`** — the `@`-prefixed open key. It parses a `PathHead` after the `@` and lowers each expanded path to a prefix type terminated by a `__Wildcard__` generic, appending that wildcard to the key's generics. A brace group in the path (`@C.{u32, u64}`) expands to several paths, each becoming its own key. This is what lets a dispatch parameter slot into the redirect path at lookup time. - -## A value: `DelegateValue` - -`DelegateValue` is the right side of a Normal or Direct mapping, an enum of either a plain `Type` or a `DelegateValueWithInnerTable`. Its `Parse` speculatively tries the inner-table form first and falls back to a bare type. - -`DelegateValueWithInnerTable` parses the legacy nested-dispatch shape `Wrapper`: a wrapper identifier, `<`, the `new` keyword, an `InnerDelegateTable`, `>`. Its `eval` produces the value type `Wrapper` (dropping the `new`, which only signals that the inner struct must be declared), and `ExtractInnerDelegateTables` yields the inner table itself so `DelegateTable::eval` emits its struct and impls alongside the outer entry. - -## `EvaluatedDelegateEntry` - -`EvaluatedDelegateEntry` is the lowered form every key/value/statement collapses to: the target type, the merged generics, the key type, the value (`Delegate`) type, and the diagnostic `span`. It owns the rendering: `build_delegate_component_impl` emits the `DelegateComponent for TableType { type Delegate = Value; }` impl, and `build_is_provider_for_impl` emits the forwarding `IsProviderFor` impl bounded on `Value: IsProviderFor`, appending the reserved `__Context__` and `__Params__` generics. Both then pass through `respan_impl`, which re-spans the finished impl's boundary tokens (its `impl` keyword and `{ … }` body) onto the entry's `span` — leaving the interior, including the value and any per-entry generic, at its own spans — so a coherence conflict points at the entry rather than the whole block, while an unconstrained generic's `E0207` still lands on the user's `` — see [Error spans](../entrypoints/delegate_components.md#error-spans). (`build_namespace_impl`, on the same type, is used by the namespace preset machinery to emit a `Namespace for Key` impl instead.) - -## Tests - -- The stack is exercised end-to-end by the expansion snapshots and behavioral tests indexed in the [entrypoint document](../entrypoints/delegate_components.md), and its attribute rejection — including the recursion through mapping values into inner tables — is pinned by the failure cases in [parser_rejections/delegate_components.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs). - -## Source - -- The stack lives in [cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/): the table and `new` keyword in `table/main.rs` and inner tables in `table/inner.rs`; the entries in `entries.rs`; the keys in `key/` (`single.rs`, `multi.rs`, `path.rs`, dispatched in `key/mod.rs`); the values in `value/` (`inner_table.rs`, dispatched in `value/mod.rs`); the mappings and their operator in `mapping/` (`normal.rs`, `direct.rs`, `redirect.rs`, `mode.rs`, and the `EvaluatedDelegateEntry` renderer in `mapping/eval.rs`); and the statements in `statement/` (`open.rs`, `namespace.rs`, `for_loop.rs`, and the shared for-entry evaluator in `statement/eval.rs`). -- Attribute rejection is in `validate_attributes.rs`, path parsing (`PathHead`, `UniPath`) in [types/path/](../../../crates/macros/cgp-macro-core/src/types/path/), and all impls are built with [parse_internal!](../macros/parse_internal.md). -- The `RedirectLookup` value the `open` and `=>` forms emit is the impl generated by [`#[cgp_component]`](../entrypoints/cgp_component.md). diff --git a/docs/implementation/asts/namespace.md b/docs/implementation/asts/namespace.md deleted file mode 100644 index 5b09a56c..00000000 --- a/docs/implementation/asts/namespace.md +++ /dev/null @@ -1,47 +0,0 @@ -# The `namespace` AST stack - -The `namespace` stack is the pair of AST types that `cgp_namespace!` parses into and renders from — `NamespaceTable`, which holds the parsed header and entries and carries all the derivation logic, and `EvaluatedNamespaceTable`, the bag of finished `syn` items it produces. A supporting type, `InheritNamespaceStatement`, lowers a parent-namespace clause into the inheritance entry. The data flows in one direction: the macro body parses into `NamespaceTable`, whose `eval` builds an `EvaluatedNamespaceTable` that a `ToTokens` impl renders. The [entrypoint document](../entrypoints/cgp_namespace.md) covers what each generated item looks like; this document covers the types. - -## `NamespaceTable` - -`NamespaceTable` is the parsed form of the whole macro body and the type that does the work. Its `Parse` impl reads, in order, an optional generic list, an optional `new` keyword, the namespace name (an identifier with optional type arguments), an optional `: parent` clause (parsed as a path with type arguments), and a brace-delimited entry table: - -```rust -pub struct NamespaceTable { - pub impl_generics: ImplGenerics, - pub new: Option>, - pub namespace: IdentWithTypeArgs, - pub parent_namespace: Option<(Colon, PathWithTypeArgs)>, - pub entries: DelegateEntries, -} -``` - -The `entries` field is a `DelegateEntries` — the same type `delegate_components!` parses its table into — so a namespace body accepts every mapping form, array key, and statement that a delegation table does. `NamespaceTable` carries a family of `build_*` methods, one per generated item: `build_item_trait` emits the `{Namespace}<__Table__>` lookup trait only when `new` is set, `build_namespace_struct` the `__{Namespace}Components` marker only when `new` is set, `build_item_impls` one impl per evaluated entry (each entry evaluated against the shared `__Table__` type), and `build_parent_namespace_impl` the inheritance impl when a parent is named. Its `eval` method runs all four and packages the results, inserting the inheritance impl ahead of the entry impls so it wins during resolution. - -## `InheritNamespaceStatement` - -`InheritNamespaceStatement` is the intermediary that turns a `: parent` clause into an inheritance entry; the user never writes it. It pairs the parent namespace path with the child's own marker-struct identifier, and its `eval_for_entry` builds a for-entry — the same `EvaluatedForEntry` shape `delegate_components!` uses — whose `where` clause bounds a key on the parent namespace over the child's local table. That for-entry then evaluates to the blanket impl that reads each parent entry and re-emits it under the child (the `__Key__`/`__Value__`/`DefaultNamespace` shape shown in the entrypoint document). Routing inheritance through the shared for-entry machinery is what keeps a namespace's parent lookup consistent with an ordinary delegation table's. - -## `EvaluatedNamespaceTable` - -`EvaluatedNamespaceTable` is the final stage — a bag holding the optional trait, the optional struct, and the vector of impls: - -```rust -pub struct EvaluatedNamespaceTable { - pub item_impls: Vec, - pub item_trait: Option, - pub item_struct: Option, -} -``` - -Its only behavior is a `ToTokens` impl that renders the items in a fixed order — struct first, then trait, then every impl — which is the order the canonical snapshots pin. Because the inheritance impl was inserted at the front of `item_impls` during `eval`, it renders ahead of the per-entry impls. - -## Tests - -- The stack is exercised end-to-end by the expansion snapshots and behavioral tests indexed in the [entrypoint document](../entrypoints/cgp_namespace.md), which pin each entry form (redirect, direct mapping, array key, inheritance, and path-prefix rewrite) and the wiring they produce. - -## Source - -- The stack lives in [cgp-macro-core/src/types/namespace/](../../../crates/macros/cgp-macro-core/src/types/namespace/): `NamespaceTable` and its `build_*`/`eval` methods in `table.rs`, `InheritNamespaceStatement` in `inherit.rs`, and `EvaluatedNamespaceTable` in `eval.rs`. -- The entry table and the for-entry machinery it reuses live in [cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/). -- The entrypoint that drives the stack is documented in [entrypoints/cgp_namespace.md](../entrypoints/cgp_namespace.md). diff --git a/docs/implementation/asts/path.md b/docs/implementation/asts/path.md deleted file mode 100644 index b63ec3e0..00000000 --- a/docs/implementation/asts/path.md +++ /dev/null @@ -1,46 +0,0 @@ -# The `path` AST stack - -The `path` stack is the family of AST types that parse and emit CGP's type-level paths. `Path!` itself drives only two of them — `UniPath`, which parses the `@`-path and emits the `PathCons` chain, and `PathElement`, which classifies each segment as a symbol or a named type — but the same segment machinery is shared by the richer path forms the namespace and prefix constructs need. This document covers the whole stack in the order the data flows, starting with the two `Path!` drives; the [entrypoint document](../entrypoints/path.md) covers what the macro produces. - -## `PathElement` - -`PathElement` is one segment of a path, either a named `Type` or a `Symbol`. Its `Parse` impl first reads the segment as a full Rust `Type`, then reclassifies: if the type reduces to a single identifier, begins with a lowercase ASCII letter, and is not a primitive type name, it becomes a `Symbol` (built via `Symbol::from_ident`, see the [`Symbol` AST](symbol.md)); otherwise it stays the named `Type`. - -```rust -app // -> Symbol (lowercase, non-primitive) -ErrorRaiserComponent // -> Type (capitalized) -u32 // -> Type (lowercase but primitive) -``` - -The primitive check recognizes the `iN`/`uN`/`fN` numeric families plus `char`, `bool`, `usize`, `isize`, and `str`. `PathElement` emits itself through `ToTokens` (delegating to the symbol or the type) and also implements the internal `ToType` trait for callers that need a `syn::Type`. Its `span()` accessor returns the segment's source span — the type's own span, or the `Symbol`'s stored parse-time span — which `delegate_components!` joins across a path to point an [error span](../entrypoints/delegate_components.md#error-spans) at an `@`-path key. - -## `UniPath` - -`UniPath` is the whole `@`-path — the type `Path!` parses into and the one `PathElement` list its `ToTokens` folds. Its `Parse` impl consumes a leading `At` token and then a dot-separated, non-empty run of `PathElement`s via `parse_separated_nonempty`, so a body without the `@` or with no segments is rejected. Its `ToTokens` right-folds the segments onto `Nil`, wrapping each in `PathCons`: - -```rust -// @app.error.ErrorRaiserComponent folds to -PathCons>> -``` - -`UniPath` also carries helpers the namespace machinery uses — `append_type` to push a trailing named segment and `to_prefix` to convert into a `PrefixPath` — but `Path!` uses only the parse-and-emit path. `PathCons` and `Nil` come from the [export markers](../../../crates/macros/cgp-macro-core/src/exports.rs). - -## `PrefixPath` - -`PrefixPath` is a `UniPath` whose chain terminates in a named suffix type rather than `Nil`, used by the `#[prefix(...)]` attribute to route a component key through a namespace path. It holds the segment list plus a `suffix: Type`, and its `ToTokens` folds the segments onto that suffix instead of `Nil`, so `@bar.baz` with a `FooProviderComponent` suffix becomes `PathCons>>` once the suffix itself expands. `Path!` never produces a `PrefixPath` directly; it is reached through `UniPath::to_prefix`. - -## `PathHead` and the branching forms - -The remaining types support the multi-path, generic-carrying grammar that `#[cgp_namespace]` and the `open` statement accept, which `Path!` does not: `PathHead` is a recursive parser for a path that can branch (a brace group of alternative sub-paths), carry a per-value group (a bracketed set sharing a tail), or attach generics, and its `into_paths` flattens such a tree into a list of `(ImplGenerics, UniPath)` pairs. `PathElementWithGenerics` pairs a `PathElement` with leading `ImplGenerics` so a segment can introduce a lifetime or type parameter. `PathHeadOrType` and `UniPathOrType` are the entry parsers that decide, by peeking for a leading `@`, whether an input is a path or a plain type — the disambiguation the namespace and wiring grammars need where either is allowed. These belong to the path stack because they reuse `PathElement` and the same `PathCons` fold, but they are driven by the namespace and wiring macros rather than by `Path!`. - -## Tests - -`Path!` is rarely written directly, so the stack is exercised mainly through the namespace and prefix machinery that reuses it. - -- [namespaces/redirect_lookup.rs](../../../crates/tests/cgp-tests/tests/namespaces/redirect_lookup.rs) pins, through a `snapshot_cgp_component!` golden, the `PathCons` chain a `#[prefix(@bar.baz in DefaultNamespace)]` attribute produces via `PrefixPath`. -- [namespaces/namespace_symbol_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_symbol_path.rs) and [namespaces/namespace_type_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_type_path.rs) exercise the lowercase-symbol and capitalized-type segment classification of `PathElement` through `#[cgp_namespace]` `@`-paths. - -## Source - -- The stack lives in [cgp-macro-core/src/types/path/](../../../crates/macros/cgp-macro-core/src/types/path/): `PathElement` in `path_element.rs`, `UniPath` in `unipath.rs`, `PrefixPath` in `prefix.rs`, `PathHead` in `path_head.rs`, `PathElementWithGenerics` in `path_element_with_generics.rs`, and the `PathHeadOrType`/`UniPathOrType` disambiguators in `path_head_or_type.rs` and `unipath_or_type.rs`. -- The runtime spine `PathCons` is defined in [cgp-base-types](../../../crates/core/cgp-base-types/src/types/path.rs). diff --git a/docs/implementation/asts/product.md b/docs/implementation/asts/product.md deleted file mode 100644 index def54c3e..00000000 --- a/docs/implementation/asts/product.md +++ /dev/null @@ -1,38 +0,0 @@ -# The `product` AST stack - -The `product` stack is the pair of AST types behind `Product!` and `product!`: `ProductType` for the type-level macro and `ProductExpr` for the value-level one. Both parse a comma-separated list and fold it into a `Cons`/`Nil` chain, but they parse and emit at different levels: `ProductType` reads a list of types and folds it into the type `Cons<…>`, while `ProductExpr` reads a list of expressions and folds it into the value `Cons(…)`. The [entrypoint document](../entrypoints/product.md) covers what the macros produce; this document covers the types. - -## `ProductType` - -`ProductType` is the type-level form. It holds the parsed element list (`types: Punctuated`), and its `Parse` impl reads that list with `parse_terminated`, so a trailing comma is accepted and an empty body yields an empty list. Its `eval` method folds the elements right-to-left onto `Nil`, wrapping each in the type form `Cons`, then re-parses the accumulated tokens into a `syn::Type` through [`parse_internal!`](../macros/parse_internal.md): - -```rust -// Product![A, B, C] evals to -Cons>> -// Product![] evals to -Nil -``` - -Returning a validated `syn::Type` rather than a raw token stream is what lets the entry function drop the result straight into `to_token_stream()`. `Cons` and `Nil` come from the [export markers](../../../crates/macros/cgp-macro-core/src/exports.rs). - -## `ProductExpr` - -`ProductExpr` is the value-level form. It holds the parsed element list as `exprs: Punctuated` and parses with `parse_terminated`, so each element is a full Rust expression — a literal, a method call, an arithmetic expression — not merely a path that also happens to parse as a type. Its `eval` folds the elements right-to-left onto `Nil` with the tuple-struct constructor form `Cons(expr, acc)`, then re-parses the accumulated tokens into a `syn::Expr` through [`parse_internal!`](../macros/parse_internal.md): - -```rust -// product![a, b, c] evals to -Cons(a, Cons(b, Cons(c, Nil))) -``` - -Parsing the items as `Expr` and re-parsing the fold as `Expr` is what keeps `product!` in expression position: the macro emits a value, so its output is dropped into an expression context, exactly as `ProductType`'s `syn::Type` output is dropped into a type context. - -## Tests - -- The `Cons`/`Nil` field spine is pinned as embedded output by the record derive snapshots ([extensible_records/person_record.rs](../../../crates/tests/cgp-tests/tests/extensible_records/person_record.rs), [extensible_records/record_derive.rs](../../../crates/tests/cgp-tests/tests/extensible_records/record_derive.rs)), which emit a `Product!` of `Field` entries. -- [handlers/pipe_handlers.rs](../../../crates/tests/cgp-tests/tests/handlers/pipe_handlers.rs) exercises the type-level `Product![…]` as a list of provider types in a handler pipeline. -- [extensible_records/product_value.rs](../../../crates/tests/cgp-tests/tests/extensible_records/product_value.rs) exercises the value-level `product!`: that expression items (a method call, an arithmetic expression) build the nested `Cons(..)`/`Nil` value, that the value's type is the matching `Product!`, and that the empty and trailing-comma forms work. - -## Source - -- The stack lives in [cgp-macro-core/src/types/product/](../../../crates/macros/cgp-macro-core/src/types/product/): `ProductType` in `product_type.rs` and `ProductExpr` in `product_expr.rs`, both re-parsing their fold through [parse_internal!](../macros/parse_internal.md). -- The runtime types `Cons` and `Nil` are defined in [cgp-base-types](../../../crates/core/cgp-base-types/src/types/). diff --git a/docs/implementation/asts/sum.md b/docs/implementation/asts/sum.md deleted file mode 100644 index c41e8ea5..00000000 --- a/docs/implementation/asts/sum.md +++ /dev/null @@ -1,26 +0,0 @@ -# The `sum` AST stack - -The `sum` stack is a single AST type, `SumType`, behind the `Sum!` macro. It parses a comma-separated list of types and folds it into an `Either`/`Void` chain — the same one-type, parse-then-`eval` shape as [`ProductType`](product.md), differing only in the spine it folds onto. The [entrypoint document](../entrypoints/sum.md) covers what the macro produces; this document covers the type. - -## `SumType` - -`SumType` holds the parsed element list (`types: Punctuated`), and its `Parse` impl reads that list with `parse_terminated`, so a trailing comma is accepted and an empty body yields an empty list. Its `eval` method folds the elements right-to-left onto `Void`, wrapping each in `Either`, then re-parses the accumulated tokens into a `syn::Type` through [`parse_internal!`](../macros/parse_internal.md): - -```rust -// Sum![A, B, C] evals to -Either>> -// Sum![] evals to -Void -``` - -The only thing that distinguishes this from `ProductType` is the terminator: a sum folds onto `Void` — the uninhabited empty enum — rather than `Nil`, so an empty sum is a type with no values while an empty product is the unit-like `Nil`. `Either` and `Void` come from the [export markers](../../../crates/macros/cgp-macro-core/src/exports.rs). - -## Tests - -- [extensible_variants/sum_macro.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/sum_macro.rs) asserts `Sum![u32, String, bool]` is the identical type to the hand-written `Either>>`, pinning the fold directly. -- [extensible_variants/has_fields_enum.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum.rs) exercises the `Sum!` variant spine an enum derives through `#[derive(HasFields)]`. - -## Source - -- The type lives in [cgp-macro-core/src/types/sum.rs](../../../crates/macros/cgp-macro-core/src/types/sum.rs), re-parsing its fold through [parse_internal!](../macros/parse_internal.md). -- The runtime types `Either` and the uninhabited `Void` are defined in [cgp-field](../../../crates/core/cgp-field/src/types/sum.rs). diff --git a/docs/implementation/asts/symbol.md b/docs/implementation/asts/symbol.md deleted file mode 100644 index 4dcee18e..00000000 --- a/docs/implementation/asts/symbol.md +++ /dev/null @@ -1,30 +0,0 @@ -# The `Symbol` AST - -The `Symbol` stack is a single AST type: `Symbol`, which parses a string literal and emits the type-level `Symbol>` string. It has no intermediate stages — one `Parse` reads the literal and one `ToTokens` emits the type — so the whole of `Symbol!`'s logic lives here. The [entrypoint document](../entrypoints/symbol.md) covers what the macro produces; this document covers the type. - -## `Symbol` - -`Symbol` holds the captured string and its span (`ident: String`, `span: Span`); it does not retain the `syn` literal. Its `Parse` impl reads a single `LitStr` and stores the literal's value and span, so a non-literal body is rejected there. - -Its `ToTokens` impl is where the type-level string is built. It folds the string's characters right-to-left onto `Nil`, wrapping each in a `Chars` node, then wraps the resulting chain in `Symbol` where `LEN` is the string's byte length from `str::len()`: - -```rust -// "ab" folds to -Chars<'a', Chars<'b', Nil>> -// then wraps to -Symbol<2, Chars<'a', Chars<'b', Nil>>> -``` - -Every emitted token carries the stored span via `quote_spanned!`, so a type error on the produced `Symbol` points back at the original literal. The `Symbol`, `Chars`, and `Nil` names come from the [export markers](../../../crates/macros/cgp-macro-core/src/exports.rs) so the output is hygienic. - -Beyond parsing a literal, `Symbol` is also constructed from a bare identifier through `Symbol::from_ident`, which the data derives and the [`path` stack](path.md) use to turn a struct field, enum variant, or lowercase path segment into a symbol. That constructor calls `Ident::unraw` before storing the identifier's text, so a raw identifier such as `r#type` is tagged by its logical name `type` — the same symbol `Symbol!("type")` produces — rather than the literal `r#type`. This is an asymmetry with the parse path: `Symbol::parse` records the string literal verbatim, so `Symbol!("r#type")` would encode the literal `r#type`, and only `from_ident` strips the prefix. The constructor keeps the identifier's span and reuses the same `ToTokens` emission, bypassing the `LitStr` parser. `Symbol` also implements the internal `ToType` trait, so a caller that needs a `syn::Type` rather than raw tokens can obtain one. - -## Tests - -- [field_access/symbol.rs](../../../crates/tests/cgp-tests/tests/field_access/symbol.rs) pins the runtime round-trip — a `Symbol!` value `Display`s back to its string and `StaticString::VALUE` recovers the literal — across the empty string, ASCII, and multi-byte Unicode, confirming the char chain rather than `LEN` drives the reconstruction. -- The `Symbol>` emission is checked as embedded output by the record and field derive snapshots indexed in the [entrypoint document's Tests section](../entrypoints/symbol.md). - -## Source - -- The type lives in [cgp-macro-core/src/types/field/symbol.rs](../../../crates/macros/cgp-macro-core/src/types/field/symbol.rs). -- The runtime types `Symbol`, `Chars`, and `Nil` are defined in [cgp-base-types](../../../crates/core/cgp-base-types/src/types/). diff --git a/docs/implementation/entrypoints/async_trait.md b/docs/implementation/entrypoints/async_trait.md deleted file mode 100644 index 612dad39..00000000 --- a/docs/implementation/entrypoints/async_trait.md +++ /dev/null @@ -1,52 +0,0 @@ -# `#[async_trait]` — implementation - -`#[async_trait]` rewrites every `async fn` declared in a trait into a plain method returning `-> impl Future`, so a trait can advertise async methods without tripping the `async_fn_in_trait` lint. This document covers how the macro is built; for the accepted syntax and the full expansion, read the reference document [reference/macros/async_trait.md](../../reference/macros/async_trait.md). - -## Entry point - -The macro lives entirely in the [cgp-async-macro](../../../crates/macros/cgp-async-macro/) crate — it does not use `cgp-macro-core` at all. The `#[proc_macro_attribute]` shim `async_trait` in [cgp-async-macro/src/lib.rs](../../../crates/macros/cgp-async-macro/src/lib.rs) discards its attribute arguments unconditionally and forwards the annotated item to `impl_async` in [cgp-async-macro/src/impl_async.rs](../../../crates/macros/cgp-async-macro/src/impl_async.rs). Because the attribute tokens are ignored, the macro is always written bare. - -## Pipeline - -There is no staged pipeline; `impl_async` is a single `syn::parse2::` followed by an in-place mutation pass. It attempts to parse the item as a trait, and on success walks the trait's methods, rewriting each `async` signature and returning the modified trait; on any parse failure it returns the original tokens unchanged. This parse-or-passthrough structure is what makes the macro composable — applying it to an `impl` block (which does not parse as an `ItemTrait`) is a no-op, so a provider's `async fn` bodies are left intact while the matching trait declaration carries the rewritten signature. - -## Generated items - -The macro emits the same trait it was given, with each `async` method signature rewritten. For a method whose signature is `async`, it clears the `async` keyword and replaces the return type `T` with `impl ::core::future::Future`; a method with no return arrow is treated as returning `()`: - -```rust -// async fn fetch(&self, id: &str) -> Result, Error>; -fn fetch(&self, id: &str) -> impl ::core::future::Future, Error>>; - -// async fn run(&self); -fn run(&self) -> impl ::core::future::Future; -``` - -Only method signatures are touched. Non-async methods, associated types, and associated constants pass through unchanged, and the method body — for the rare method that has one — is never rewritten (see Known issues). - -## Behavior and corner cases - -The **only structural transform is to the signature**: the `async` token is removed and the return type is wrapped in `impl Future`. The macro introduces no boxing, no allocation, and no `Send` bound — it is a mechanical spelling of return-position `impl Trait` in a trait, and the returned future is exactly the one the method body produces. - -The **passthrough on non-trait items is deliberate**, not an error path in the usual sense: any item that fails to parse as an `ItemTrait` is returned verbatim, which is what lets `#[async_trait]` sit harmlessly on an `impl` block that a host macro like [`#[cgp_fn]`](cgp_fn.md) copies the attribute onto. - -## Known issues - -The macro **mishandles an async method that carries a default body**. It strips `async` and rewrites the return type to `impl Future`, but leaves the body verbatim rather than wrapping it in an `async { … }` block. The result is a non-async method whose body still returns a plain value (and may use `.await`), which fails to compile. In practice this is rarely hit — async trait methods are almost always bodyless declarations, with behavior supplied by a provider impl — but a default-bodied `async fn` inside a `#[async_trait]` trait is not supported. The correct behavior would be to wrap a rewritten method's body in `async move { … }` so the signature and body agree. The user-visible consequence, together with the absence of a `Send` bound on the generated future, is documented under Known issues in [reference/macros/async_trait.md](../../reference/macros/async_trait.md). - -## Tests - -The macro is exercised through its interaction with the constructs it stacks onto rather than on its own: - -- [async_and_send/cgp_fn_async.rs](../../../crates/tests/cgp-tests/tests/async_and_send/cgp_fn_async.rs) — a `snapshot_cgp_fn!` over an async `#[cgp_fn]` that also carries `#[async_trait]`, pinning the rewritten trait declaration; this snapshot is owned by the `#[cgp_fn]` feature, not by `#[async_trait]`. -- [async_and_send/spawn.rs](../../../crates/tests/cgp-tests/tests/async_and_send/spawn.rs) — async components declared with `#[async_trait]` whose futures are handed to a `Send + 'static`-demanding executor. -- [dispatching/auto_dispatch_async_self_ref_only.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_ref_only.rs) and the other `auto_dispatch_async_*` files — `#[async_trait]` stacked with [`#[cgp_auto_dispatch]`](cgp_auto_dispatch.md) on async dispatch traits. - -There is no dedicated `snapshot_async_trait!` macro; the rewrite is only pinned incidentally through the `#[cgp_fn]` snapshot above. - -## Source - -- Entry point: `async_trait` in [cgp-async-macro/src/lib.rs](../../../crates/macros/cgp-async-macro/src/lib.rs). -- The rewrite: `impl_async` in [cgp-async-macro/src/impl_async.rs](../../../crates/macros/cgp-async-macro/src/impl_async.rs). -- Re-exported into the prelude from [crates/main/cgp-core/src/prelude.rs](../../../crates/main/cgp-core/src/prelude.rs). -- It is most often stacked with [`#[cgp_component]`](cgp_component.md) and [`#[cgp_fn]`](cgp_fn.md). diff --git a/docs/implementation/entrypoints/blanket_trait.md b/docs/implementation/entrypoints/blanket_trait.md deleted file mode 100644 index 1c5912a2..00000000 --- a/docs/implementation/entrypoints/blanket_trait.md +++ /dev/null @@ -1,90 +0,0 @@ -# `#[blanket_trait]` — implementation - -`#[blanket_trait]` turns a trait whose items carry default definitions into an extension trait by emitting the trait unchanged and generating the blanket impl that forwards those defaults, hiding the trait's supertraits behind its `where` clause. This document covers how that works internally; for the accepted syntax and the full expansion, read the reference document [reference/macros/blanket_trait.md](../../reference/macros/blanket_trait.md). - -## Entry point - -The macro is driven by the `blanket_trait` function in [cgp-macro-lib/src/blanket_trait.rs](../../../crates/macros/cgp-macro-lib/src/blanket_trait.rs). It parses the attribute argument as an optional context identifier — defaulting to the reserved `__Context__` when the attribute is empty — and the item as a `syn::ItemTrait`, then builds an `ItemBlanketTrait` and renders it directly. - -```rust -let context_ident = if attr.is_empty() { - Ident::new("__Context__", Span::call_site()) -} else { - parse2(attr)? -}; -let item_blanket_impl = ItemBlanketTrait { context_ident, item_trait }; -let items = item_blanket_impl.to_items()?; -``` - -Two failures surface here: a non-identifier attribute argument fails at `parse2`, and a non-trait item fails at `syn::parse2::`. Unlike `#[cgp_component]`, this macro has no multi-stage pipeline — the single `to_items` call does all the work. - -## Pipeline - -There is only one transform. `ItemBlanketTrait::to_items` emits two items: the trait, cloned verbatim from the input, and the blanket impl produced by `to_item_impl`. Because the trait is emitted unchanged, its declarations keep their default bodies as the user wrote them; the derivation happens only on the impl side. The [`blanket_trait` AST stack](../asts/blanket_trait.md) documents `ItemBlanketTrait`. - -## Generated items - -The macro emits the trait unchanged followed by one blanket impl over the context type. The impl is where all the derivation happens: it forwards each trait item's default into an impl item, requires the trait's supertraits on the context, and lifts each associated type into a fresh impl generic. The impl generics are the trait's own generics plus the context parameter plus one parameter per associated type, and the trait's supertraits become a `#context: ` predicate. - -For a method-carrying trait the impl copies each default method body verbatim, so every qualifying context inherits the body: - -```rust -// #[blanket_trait] pub trait FooBar: Foo + Bar { fn foo_bar(&self) { self.foo(); self.bar(); } } - -impl<__Context__> FooBar for __Context__ -where - __Context__: Foo + Bar, -{ - fn foo_bar(&self) { self.foo(); self.bar(); } -} -``` - -An **associated type** is handled specially, because lifting one out of a supertrait is the pattern's main use. Each associated type in the trait becomes a new generic parameter on the impl; a `RemoveSelfPathVisitor` pass rewrites `Self::FooBar` references (in the supertrait's associated-type equality, for instance) to name that free parameter; and the impl assigns the parameter back to the associated type: - -```rust -// #[blanket_trait] pub trait HasFooTypeAtBar: HasFooTypeAt { type FooBar; } - -impl<__Context__, FooBar> HasFooTypeAtBar for __Context__ -where - __Context__: HasFooTypeAt, -{ - type FooBar = FooBar; -} -``` - -An **associated constant** is forwarded like a method: its default expression becomes the impl's constant definition. - -## Behavior and corner cases - -A **method or constant without a default** is a hard error: `to_item_impl` calls `ok_or_else` on the missing `default`, so the macro rejects it with a spanned `syn::Error` ("function item require implementation block" / "const item require implementation expression") rather than emitting a broken impl. An **associated type** needs no default, because the macro supplies the assignment itself from the lifted parameter. - -A **bound on an associated type** is moved onto the lifted parameter in the impl's `where` clause: `type FooBar: Clone` adds `FooBar: Clone` alongside the supertrait predicate, so the blanket impl only applies when the underlying type is `Clone`. The bound is collected from the trait item before its `default` is stripped for the impl copy. - -The **supertraits** of the trait become the hidden dependency: they are cloned into a single `#context: ` `where`-predicate on the impl, which is what shields callers from naming `Foo + Bar`. The trait keeps its supertraits in the declaration too, since the trait is emitted unchanged. - -Any trait item other than a type, method, or constant — a macro invocation, say — is rejected with an "unsupported trait item" error. - -## Snapshots - -Every `snapshot_blanket_trait!` invocation across the suite is indexed here; all live in the `blanket_traits` target: - -- [blanket_traits/basic.rs](../../../crates/tests/cgp-tests/tests/blanket_traits/basic.rs) — the canonical minimal expansion: supertrait bounds only, no body, yielding an empty impl (a trait alias in all but name). -- [blanket_traits/with_method.rs](../../../crates/tests/cgp-tests/tests/blanket_traits/with_method.rs) — a default method body copied verbatim into the blanket impl. -- [blanket_traits/associated_type.rs](../../../crates/tests/cgp-tests/tests/blanket_traits/associated_type.rs) — a local associated type lifted into an impl generic and tied to a supertrait's associated type via `Foo = Self::FooBar`. -- [blanket_traits/associated_type_bounded.rs](../../../crates/tests/cgp-tests/tests/blanket_traits/associated_type_bounded.rs) — the same, plus a bound (`type FooBar: Clone`) moved onto the lifted parameter in the `where` clause. - -Two variants have no snapshot yet: an associated *constant* forwarded from its default expression, and the `#[blanket_trait(Ctx)]` form overriding the default context identifier. - -## Tests - -The snapshot files double as behavioral tests: - -- [blanket_traits/basic.rs](../../../crates/tests/cgp-tests/tests/blanket_traits/basic.rs) and the others each wire a concrete `Context` and assert through a `CanUse…` check trait that the generated blanket impl applies. -- No `cgp-macro-tests` failure case pins the "missing default body" error path, which is a candidate to add. - -## Source - -- Entry point: `blanket_trait` in [cgp-macro-lib/src/blanket_trait.rs](../../../crates/macros/cgp-macro-lib/src/blanket_trait.rs). -- Logic: [cgp-macro-core/src/types/blanket_trait.rs](../../../crates/macros/cgp-macro-core/src/types/blanket_trait.rs), documented in [asts/blanket_trait.md](../asts/blanket_trait.md). -- `Self::AssocType`-to-parameter rewriting: `RemoveSelfPathVisitor` in [cgp-macro-core/src/visitors/remove_self_path.rs](../../../crates/macros/cgp-macro-core/src/visitors/remove_self_path.rs). -- Fragment construction: [parse_internal!](../macros/parse_internal.md). diff --git a/docs/implementation/entrypoints/cgp_auto_dispatch.md b/docs/implementation/entrypoints/cgp_auto_dispatch.md deleted file mode 100644 index 5aed5ef4..00000000 --- a/docs/implementation/entrypoints/cgp_auto_dispatch.md +++ /dev/null @@ -1,79 +0,0 @@ -# `#[cgp_auto_dispatch]` — implementation - -`#[cgp_auto_dispatch]` takes a trait with one impl per payload type and appends the code that makes the trait work on an enum of those payloads: a blanket impl of the trait for a fresh enum parameter that runs a value-handler matcher, plus one per-variant [`Computer`](../../reference/components/computer.md) per method. This document covers how the macro is built; for the accepted syntax and the full expansion, read the reference document [reference/macros/cgp_auto_dispatch.md](../../reference/macros/cgp_auto_dispatch.md). - -## Entry point - -The macro is the `cgp_auto_dispatch` function in [cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs). It is a self-contained procedural function operating directly on `syn` types rather than a driver over a `cgp-macro-core` AST stack. It parses the annotated item as a `syn::ItemTrait`, keeps the original trait tokens verbatim, and appends generated code to them. The attribute takes no arguments. - -The macro is additive: the trait itself is unchanged, so the per-payload impls the user writes still satisfy it directly. Everything the macro produces is emitted *after* the trait — the enum-level blanket impl first, then one per-variant computer per method. - -## Pipeline - -There is no staged AST pipeline; the entry function walks the trait's methods twice. The generated code is built by two internal helpers, each of which is worth naming because it emits one of the two kinds of output: - -- **`derive_blanket_impl`** builds the single `impl for __Variants__` that, per method, invokes a value-handler matcher over the enum. It threads a `where` clause that bounds each matcher and requires `__Variants__: HasExtractor`. -- **`derive_method_computer`** builds, per method, a free function annotated with [`#[cgp_computer]`](cgp_computer.md) whose body calls the trait method on the payload. The macro emits these by delegating to `#[cgp_computer]` rather than synthesizing the `Computer` impl itself. - -Both helpers reject a trait item that is not a method, and reject a method that has no `self` receiver or carries a non-lifetime generic parameter. - -## Generated items - -For each method the macro emits a per-variant computer named `Compute` followed by the method name in PascalCase (`area` yields `ComputeArea`). Its body is just the trait-method call on the payload, and it is bound so it applies to every payload type implementing the trait; a `&self` method borrows the payload through a fresh lifetime `'__a__`: - -```rust -// from `fn area(&self) -> f64;` -#[cgp_computer(ComputeArea)] -fn area<'__a__, __Variants__: HasArea>(__Variants__: &'__a__ __Variants__) -> f64 { - __Variants__.area() -} -``` - -The enum-level blanket impl implements the trait for a fresh parameter `__Variants__` and, in each method body, dispatches through the matcher struct selected for that method's shape, invoking it with a unit context `&()` and unit code `PhantomData::<()>`: - -```rust -impl<__Variants__> HasArea for __Variants__ -where - MatchWithValueHandlersRef: - for<'__a__> Computer<(), (), &'__a__ __Variants__, Output = f64>, - __Variants__: HasExtractor, -{ - fn area(&self) -> f64 { - MatchWithValueHandlersRef::::compute(&(), PhantomData::<()>, self) - } -} -``` - -The matcher is chosen from the value-handler family by two properties of the method: the receiver form and whether the method takes extra arguments. With no extra arguments the plain family is used — `MatchWithValueHandlersRef` for `&self`, `MatchWithValueHandlersMut` for `&mut self`, `MatchWithValueHandlers` for by-value `self`. With extra arguments the first-argument family is used instead — `MatchFirstWithValueHandlersRef`/`Mut`/(plain) — and the receiver and arguments are bundled into the matcher input as `(context, (args…))`. An `async` method selects the `AsyncComputer` form of both the `where` bound and the matcher call, and `.await`s the result. - -## Behavior and corner cases - -The **matcher input reflects the receiver and argument list**. A no-argument method passes the bare payload as the input; a method with arguments passes a nested tuple `(self, (arg_0, arg_1, …))`, and the arguments are rebound positionally as `arg_i` (the source patterns are discarded). - -**Reference lifetimes are elaborated** so the matcher bound can be quantified. A reference receiver, reference argument, or reference return type with an elided lifetime is rewritten to carry the fresh `'__a__`, and a `for<'__a__>` quantifier is added to the matcher bound when any such lifetime is introduced. A `'static` lifetime is excluded from the quantifier. - -The **enum must be extensible**: the blanket impl always carries `__Variants__: HasExtractor`, so the enum needs a `CgpData`/`CgpVariant`-style derive that supplies the extractor and field machinery. Forgetting a per-variant impl surfaces as an unsatisfied matcher bound at the point the enum's method is used, not at the trait definition. - -## Known issues - -The macro **rejects a trait method with non-lifetime generic parameters** with a spanned `syn::Error` ("Dispatch trait methods cannot contain non-lifetime generic parameters due to the lack of quantified constraints in Rust"). This is a deliberate limitation, not a parser gap: the generated blanket impl would need a bound quantified over the method's type parameter to guarantee every payload satisfies it for all instantiations, and Rust has no such quantified bound. The user-visible consequence is documented under Known issues in [reference/macros/cgp_auto_dispatch.md](../../reference/macros/cgp_auto_dispatch.md); a method that must be generic has to be wired with the dispatch combinators directly instead. - -## Tests - -The behavioral tests cover every receiver-and-argument shape the matcher selection distinguishes: - -- [dispatching/auto_dispatch_shape.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_shape.rs) — a realistic `Shape` enum with a `&self` reader (`area`) and a `&mut self` mutator (`scale`). -- [dispatching/auto_dispatch_self_only.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_only.rs), [dispatching/auto_dispatch_self_ref_only.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_only.rs), [dispatching/auto_dispatch_self_mut_only.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_mut_only.rs) — the by-value, `&self`, and `&mut self` no-argument forms, selecting the three plain matchers. -- [dispatching/auto_dispatch_multi_args.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args.rs), [dispatching/auto_dispatch_multi_args_ref.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args_ref.rs), [dispatching/auto_dispatch_multi_args_owned_self.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_args_owned_self.rs) — the argument-taking forms, selecting the first-argument matcher family. -- [dispatching/auto_dispatch_self_ref_return_implicit_ref.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_return_implicit_ref.rs), [dispatching/auto_dispatch_self_ref_return_explicit_ref.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_self_ref_return_explicit_ref.rs) — a reference return type with an elided versus an explicit lifetime, pinning the `'__a__` elaboration. -- [dispatching/auto_dispatch_multi_methods.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_multi_methods.rs) — a trait mixing `&self`, `&mut self`, and `self` methods over one enum. -- [dispatching/auto_dispatch_generics.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_generics.rs) — a generic *trait* (`CanCall`) whose per-variant impls add their own bounds; distinct from a generic *method*, which is rejected. -- [dispatching/auto_dispatch_async_self_only.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_only.rs), [dispatching/auto_dispatch_async_self_ref_only.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_ref_only.rs), [dispatching/auto_dispatch_async_self_mut_only.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_self_mut_only.rs), [dispatching/auto_dispatch_async_multi_args.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args.rs), [dispatching/auto_dispatch_async_multi_args_ref.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args_ref.rs), [dispatching/auto_dispatch_async_multi_args_owned_self.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_multi_args_owned_self.rs), [dispatching/auto_dispatch_async_generics.rs](../../../crates/tests/cgp-tests/tests/dispatching/auto_dispatch_async_generics.rs) — the same shapes stacked with [`#[async_trait]`](async_trait.md), selecting the `AsyncComputer` matcher form. - -There is no dedicated `snapshot_cgp_auto_dispatch!` macro; the macro's expansion is not pinned by a snapshot and is exercised only behaviorally. - -## Source - -- Entry point: `cgp_auto_dispatch` in [cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs), forwarded from the proc-macro shim in [cgp-extra-macro/src/lib.rs](../../../crates/macros/cgp-extra-macro/src/lib.rs). -- The per-variant handlers are emitted through [`#[cgp_computer]`](cgp_computer.md). -- The value-handler matchers it wires: [crates/extra/cgp-dispatch/src/providers/matchers/](../../../crates/extra/cgp-dispatch/src/providers/matchers/). diff --git a/docs/implementation/entrypoints/cgp_auto_getter.md b/docs/implementation/entrypoints/cgp_auto_getter.md deleted file mode 100644 index 1e694278..00000000 --- a/docs/implementation/entrypoints/cgp_auto_getter.md +++ /dev/null @@ -1,95 +0,0 @@ -# `#[cgp_auto_getter]` — implementation - -`#[cgp_auto_getter]` derives a single blanket getter impl over `HasField` from a trait's method names, so any context whose field names match the getters gains the trait with no wiring. This document covers how that works internally; for the accepted syntax and the complete expansion a user sees, read the reference document [reference/macros/cgp_auto_getter.md](../../reference/macros/cgp_auto_getter.md). - -## Entry point - -The macro is driven by the `cgp_auto_getter` function in [cgp-macro-lib/src/cgp_auto_getter.rs](../../../crates/macros/cgp-macro-lib/src/cgp_auto_getter.rs), which takes no attribute argument, parses the item into a `syn::ItemTrait`, then runs the two-step transform and emits the result. - -```rust -let item = ItemCgpAutoGetter::preprocess(&item_trait)?; -let items = item.to_items()?; -``` - -The macro rejects any attribute argument up front with a spanned error — unlike `#[cgp_getter]`, it has no provider name, component, or keys to accept. Applying it to a non-trait item fails at `parse2::`. All real logic lives in `cgp-macro-core`. - -## Pipeline - -The macro moves through two short steps, both methods on `ItemCgpAutoGetter`, documented in the [`cgp_getter` AST stack](../asts/cgp_getter.md) alongside `#[cgp_getter]`. - -- **preprocess** strips the CGP modifier attributes off the trait and keeps the cleaned trait; there is no component to derive, so this stage does little more than the attribute split. -- **to_items** parses the trait's methods into `GetterField`s and emits the trait unchanged followed by one blanket impl of that trait for `__Context__`. - -## Generated items - -`#[cgp_auto_getter]` emits exactly two items: the trait as written, and a single blanket impl over `__Context__` that implements every getter by reading the like-named field. Unlike `#[cgp_getter]`, there is no provider trait, no component marker, and no `UseField`/`UseFields`/`WithProvider` impls — the field each getter reads is fixed to the method name, so nothing is left to wire. - -Each getter body reads its field with `get_field(PhantomData::)` and appends the field-mode conversion for its return type, and the field itself becomes a `HasField` bound on the impl: - -```rust -// input -#[cgp_auto_getter] -pub trait HasFoo { - fn foo(&self) -> &str; -} - -// derived blanket impl -impl<__Context__> HasFoo for __Context__ -where - __Context__: HasField, -{ - fn foo(&self) -> &str { - self.get_field(PhantomData::).as_str() - } -} -``` - -The conversions are exactly those `#[cgp_getter]` uses — `&str` reads a `String` field and appends `.as_str()`, `Option<&T>` reads `Option` and appends `.as_ref()`, `&[T]` reads an `AsRef<[T]>` field, `MRef<'_, T>` wraps the borrow in `MRef::Ref`, an owned return appends `.clone()`, and a plain `&T` is taken by reference — plus the mutable mirrors selected by a `&mut self` receiver (`&mut [T]` reads an `AsMut<[T]>` field via `.as_mut()`, `Option<&mut T>` via `.as_mut()`, `Option<&mut str>` via `.as_deref_mut()`) — because both macros share the [getter-field parsing](../asts/cgp_getter.md). The difference is only that `#[cgp_auto_getter]` emits a blanket impl on `__Context__` while `#[cgp_getter]` emits provider impls. - -## Behavior and corner cases - -**A supertrait on the getter trait becomes a context bound on the impl.** The trait's supertrait constraints are lowered to a `__Context__: Supertrait` predicate on the blanket impl, so a getter trait can require capabilities of any context that implements it without those constraints appearing on the impl's `Self` in trait position. - -**A single associated return type is supported and inferred.** A getter trait may declare one `type Name;` used as its method's return type; the blanket impl adds `Name` as a generic parameter, sets `type Name = Name;`, and carries any bound on it (for example `Name: Display`, or a self-referential `Scalar: Mul`) onto the impl with `Self::Name` rewritten to the parameter. More than one associated type, or an associated type alongside more than one method, is rejected. - -**A generic parameter on the trait is preserved onto the impl.** A trait generic over a type parameter keeps that parameter on the blanket impl and threads it through the field bound, so the same getter works for any field type inferred from the argument. - -**A getter can read a field of another type.** A method with a typed receiver (`fn foo_bar(foo: &Self::Foo) -> &Self::Bar`) reads the field out of that receiver type; `Self` in the receiver is rewritten to the context, and the `HasField` bound lands on the receiver type rather than the context. - -**Unsupported field-type combinations are deferred to the compiler.** The `Option` and slice shorthands each cover a single field shape; a combination CGP provides no rule for — most notably `Option<&[T]>` — is lowered literally rather than given a bespoke rule, so it reaches the compiler as invalid Rust rather than a macro-time rejection. `parse_field_type` applies the `Option<&T>` rule to `Option<&[T]>` and emits a `HasField` bound over the unsized `Option<[T]>`, which `rustc` rejects. This is a deliberate boundary shared by `#[cgp_getter]` and `#[cgp_fn]` implicits (all three call `parse_field_type`), pinned as an acceptable failure by the `cargo-cgp` UI fixture [`usability/lowering/option_slice.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/lowering/option_slice.rs) — filed under `usability/` because cargo-cgp passes rustc's `Sized` error through rather than rewriting it. - -## Snapshots - -Every `snapshot_cgp_auto_getter!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint: - -- [getters/string_auto.rs](../../../crates/tests/cgp-tests/tests/getters/string_auto.rs) — the canonical plain case: a `&str` getter over a `String` field, `.as_str()` applied, no wiring. -- [getters/clone_auto.rs](../../../crates/tests/cgp-tests/tests/getters/clone_auto.rs) — an owned return `.clone()`d out by value. -- [getters/mref_auto.rs](../../../crates/tests/cgp-tests/tests/getters/mref_auto.rs) — an `MRef<'_, String>` return wrapping the borrow in `MRef::Ref`. -- [getters/option_auto.rs](../../../crates/tests/cgp-tests/tests/getters/option_auto.rs) — an `Option<&String>` return reading an `Option` field via `.as_ref()`. -- [getters/option_str_auto.rs](../../../crates/tests/cgp-tests/tests/getters/option_str_auto.rs) — an `Option<&str>` return reading an `Option` field via `.as_deref()`, composing the `&str` and option cases. -- [getters/slice_auto.rs](../../../crates/tests/cgp-tests/tests/getters/slice_auto.rs) — a `&[u8]` return reading an `AsRef<[u8]> + 'static` field via `.as_ref()`. -- [getters/mut_str_auto.rs](../../../crates/tests/cgp-tests/tests/getters/mut_str_auto.rs) — a `&mut self` getter returning `&mut str`, reading the `String` field through `HasFieldMut`/`get_field_mut` and calling `.as_mut_str()`. -- [getters/mut_slice_auto.rs](../../../crates/tests/cgp-tests/tests/getters/mut_slice_auto.rs) — a `&mut self` getter returning `&mut [u32]`, bounding the field by `AsMut<[u32]> + 'static` and calling `.as_mut()`. -- [getters/mut_option_auto.rs](../../../crates/tests/cgp-tests/tests/getters/mut_option_auto.rs) — a `&mut self` getter returning `Option<&mut u32>`, reading the `Option` field via `.as_mut()`. -- [getters/non_self_auto.rs](../../../crates/tests/cgp-tests/tests/getters/non_self_auto.rs) — a non-`self` getter reading a field out of another type (`&Self::Foo`). -- [getters/auto_getter_generic.rs](../../../crates/tests/cgp-tests/tests/getters/auto_getter_generic.rs) — a trait generic over a type parameter, keyed by a `PhantomData` tag. -- [getters/assoc_type_auto_getter.rs](../../../crates/tests/cgp-tests/tests/getters/assoc_type_auto_getter.rs) — a local associated return type inferred from the field, with a `Display` bound carried onto the impl. -- [getters/assoc_type_self_referential_auto.rs](../../../crates/tests/cgp-tests/tests/getters/assoc_type_self_referential_auto.rs) — a self-referential associated-type bound surviving onto the impl with `Self::Scalar` rewritten to the parameter. -- [getters/abstract_type_extend.rs](../../../crates/tests/cgp-tests/tests/getters/abstract_type_extend.rs), [getters/abstract_type_use_type.rs](../../../crates/tests/cgp-tests/tests/getters/abstract_type_use_type.rs) — getters whose return type is an abstract type imported via `#[extend]` and `#[use_type]`; each file pins both the auto and the full getter variant. - -Coverage is broad across return shapes and both receiver mutabilities. The two mutable conversions with no dedicated snapshot yet are a plain `&mut T` return (a bare `get_field_mut` with no conversion) and an `Option<&mut str>` return (read via `.as_deref_mut()`, the mutable mirror of `option_str_auto.rs`). - -## Tests - -Each snapshot file also derives a concrete context and asserts the getter resolves, so the snapshot files carry the behavioral checks too: - -- [getters/string_auto.rs](../../../crates/tests/cgp-tests/tests/getters/string_auto.rs) asserts `context.foo()` returns the `foo` field's contents as `&str`. -- [getters/assoc_type_auto_getter.rs](../../../crates/tests/cgp-tests/tests/getters/assoc_type_auto_getter.rs) confirms the associated type is inferred and the getter compiles under the `Display` bound. -- [getters/auto_getter_generic.rs](../../../crates/tests/cgp-tests/tests/getters/auto_getter_generic.rs) confirms the generic-parameter getter applies to any inferred field type. - -## Source - -- Entry point: `cgp_auto_getter` in [cgp-macro-lib/src/cgp_auto_getter.rs](../../../crates/macros/cgp-macro-lib/src/cgp_auto_getter.rs), which rejects any attribute argument and runs `ItemCgpAutoGetter::preprocess(...).to_items()`. -- The stack: [cgp-macro-core/src/types/cgp_auto_getter/](../../../crates/macros/cgp-macro-core/src/types/cgp_auto_getter/), documented in [asts/cgp_getter.md](../asts/cgp_getter.md): `item.rs` sets the `__Context__` identifier and drives field parsing, and `blanket.rs` builds the single blanket impl. -- Getter-method parsing and the return-type shorthands, shared with `#[cgp_getter]`: [cgp-macro-core/src/functions/getter/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/getter/parse.rs), [cgp-macro-core/src/functions/field/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/field/parse.rs), and [cgp-macro-core/src/types/getter/](../../../crates/macros/cgp-macro-core/src/types/getter/). -- Fragment construction: [parse_internal!](../macros/parse_internal.md). diff --git a/docs/implementation/entrypoints/cgp_component.md b/docs/implementation/entrypoints/cgp_component.md deleted file mode 100644 index d2026a1f..00000000 --- a/docs/implementation/entrypoints/cgp_component.md +++ /dev/null @@ -1,99 +0,0 @@ -# `#[cgp_component]` — implementation - -`#[cgp_component]` turns one consumer trait into a full component by parsing the trait, deriving the provider trait and the blanket impls that route between the two sides, and emitting the standard provider impls. This document covers how that works internally; for the accepted syntax and the complete expansion a user sees, read the reference document [reference/macros/cgp_component.md](../../reference/macros/cgp_component.md). - -## Entry point - -The macro is driven by the thin `cgp_component` function in [cgp-macro-lib/src/cgp_component.rs](../../../crates/macros/cgp-macro-lib/src/cgp_component.rs), which follows the canonical entry-point shape: it parses the attribute into a `CgpComponentArgs` and the item into a `syn::ItemTrait`, then runs the pipeline and emits the result. - -```rust -let item_cgp_component = ItemCgpComponent { args, item_trait }; -let derived = item_cgp_component.preprocess()?.eval()?.to_items()?; -``` - -All real logic lives in `cgp-macro-core`. Two failures surface here: a malformed attribute is rejected while parsing `CgpComponentArgs` (the argument grammar is enforced by its `Parse` impl), and applying the macro to a non-trait item fails at `syn::parse2::`. - -## Pipeline - -The macro moves through three stages, each a method on the AST type the previous one produced; the [`cgp_component` AST stack](../asts/cgp_component.md) documents those types in full. - -- **preprocess** strips the CGP modifier attributes (`#[derive_delegate]`, `#[prefix]`, and the rest) off the trait, separating them from the plain trait the later stages transform. -- **eval** is the core derivation: it builds the provider trait, the consumer and provider blanket impls, and the component marker struct. -- **to_items** renders everything into the final `Vec`, appending the standard provider impls (`UseContext`, `RedirectLookup`, and one `UseDelegate` or prefix impl per modifier attribute). - -## Generated items - -The macro emits five core items in a fixed order — consumer trait, consumer blanket impl, provider trait, provider blanket impl, component struct — followed by the standard provider impls. The order is what the canonical snapshot pins, so it is a contract rather than an incidental detail. - -The interesting work is deriving the provider trait from the consumer trait: the original `Self` becomes an explicit leading context parameter, `self`/`Self` in every signature are rewritten to the context (via the `replace_self` visitors), and the trait's sole supertrait becomes an [`IsProviderFor`](../../reference/traits/is_provider_for.md) bound that captures the component and its parameters. A method that took `&self` ends up taking the context by value-name: - -```rust -// consumer trait -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -// derived provider trait -pub trait AreaCalculator<__Context__>: - IsProviderFor -{ - fn area(__context__: &__Context__) -> f64; -} -``` - -The two blanket impls are the routing machinery and are forwarding shells: the consumer impl makes any context that implements the provider trait for itself gain the consumer trait, and the provider impl lets any provider that delegates the component (via [`DelegateComponent`](../../reference/traits/delegate_component.md)) inherit the provider trait from its delegate. Both forward each method body to the chosen type through the shared [delegated-impl helpers](../functions/derive/delegated_impls.md), which is why their bodies all read as `::method(context, …)`. The `UseContext` and `RedirectLookup` provider impls are two more forwarding shells over the same helpers — one routing back through the context's own consumer impl, the other along a namespace lookup path. - -## Behavior and corner cases - -A **supertrait** on the consumer trait is not kept as a supertrait on the provider trait; it is lowered to a bound on the context, and the same context bound is threaded onto every generated impl. So `pub trait CanGreet: HasName` produces: - -```rust -pub trait Greeter<__Context__>: IsProviderFor -where - __Context__: HasName, -{ /* … */ } -``` - -A **default method body** is preserved into the provider trait, because the provider trait is a clone of the consumer trait with only `self`/`Self` rewritten. This is what lets an empty `#[cgp_impl]` provider inherit the default and satisfy the component with no method of its own. - -**Generic parameters** on the component are appended after the context in the provider trait and collected into the `IsProviderFor` params tuple. A lifetime stays ahead of the context (lifetimes must precede type parameters) and is lifted into `Life<'a>` in that tuple, so `HasReference<'a, T>` yields `IsProviderFor<…, (Life<'a>, T)>`. Only *type* parameters, not lifetimes or const parameters, extend the `RedirectLookup` lookup path. - -A **const generic parameter** on the component is rejected with a spanned `syn::Error` while building the params tuple, since a const value has no representation in a tuple of types and cannot key CGP's type-based wiring (see [parse_is_provider_params](../functions/parse/is_provider_params.md)). This is the idiomatic way to reject it rather than emitting a provider trait that uses the const parameter in type position and fails to compile. A const *item* on the trait (`const CONSTANT: u64;`) is unaffected — that is an associated const, not a generic parameter, and is provided by a const-generic provider struct. - -The **reserved identifiers** appear literally in the output: the context parameter is `__Context__` (unless the `context` key overrides it), the provider parameter is `__Provider__`, and the `RedirectLookup` impl introduces `__Components__` and `__Path__`. These names are chosen so they never clash with a user's own type parameters. - -The **marker struct's name span** is the provider identifier's span, not `Span::call_site()`. When the component name is not given explicitly, `CgpComponentArgs` derives it as `{Provider}Component` and spans that identifier on the provider identifier the user wrote in `#[cgp_component(Provider)]`. A `call_site` span would instead cover the whole `#[cgp_component(..)]` attribute, so the generated `struct {Provider}Component` would report its definition on a range that also contains the `cgp_component` macro path — and rust-analyzer go-to-definition on a delegate key naming the component would then offer both the component and the macro as targets. That misdirection appears only when navigating from another crate, where the editor relies on the recorded definition span rather than a live expansion; deriving the span from the provider identifier keeps the definition on a single user token, mirroring `derive_check_trait_ident`. See the [Spans note](../README.md#spans-aim-generated-items-at-the-token-the-user-wrote) for why span placement serves the editor as well as the compiler. - -## Failure modes - -A **name clash on the derived marker** — a module that declares its own `GreeterComponent` alongside a `#[cgp_component(Greeter)]` that derives the same marker — defines the name twice and fails with `E0428`, the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. What this document pins is the **span**: the `E0428` "previous definition here" note lands on the `Greeter` provider name inside `#[cgp_component(..)]`, not on the whole attribute, per the marker-span behavior above. The `cargo-cgp` UI fixture [`acceptable/wiring/duplicate-keys/duplicate_component_name.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_component_name.rs) is the regression test for that span, since a regression to `call_site` would move the note onto the attribute. - -## Snapshots - -Every `snapshot_cgp_component!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint: - -- [basic_delegation/component_macro.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/component_macro.rs) — the canonical plain expansion (one method, no parameters). -- [basic_delegation/default_methods.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/default_methods.rs) — a supertrait lowered to a context `where`-bound plus a default method body copied into the provider trait. -- [generic_components/component_type_param.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_type_param.rs) — a single type parameter, appended after `__Context__`, placed in the params tuple as `(Shape)`, and extending the `RedirectLookup` path via `ConcatPath>`, with no lifetime present. -- [generic_components/component_lifetime.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs) — a lifetime kept ahead of `__Context__`, lifted to `Life<'a>`, with a type parameter extending the `RedirectLookup` path via `ConcatPath`. -- [namespaces/namespace_basic.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_basic.rs), [namespaces/namespace_symbol_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_symbol_path.rs), [namespaces/namespace_type_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_type_path.rs), [namespaces/namespace_multi.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_multi.rs), [namespaces/redirect_lookup.rs](../../../crates/tests/cgp-tests/tests/namespaces/redirect_lookup.rs), [namespaces/default_impls.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls.rs), [namespaces/prefix_default_namespace.rs](../../../crates/tests/cgp-tests/tests/namespaces/prefix_default_namespace.rs) — the namespace and prefix-impl variants. - -One variant has no snapshot yet: the `UseDelegate` impl a `#[derive_delegate]` attribute adds to a bare component, exercised through the error and handler families instead. - -## Tests - -The behavioral tests confirm the generated wiring works: - -- [basic_delegation/default_methods.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/default_methods.rs) checks at run time that an empty provider impl inherits the default bodies and `App.greet()` returns the expected string. -- [generic_components/component_type_param.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_type_param.rs) wires a single-type-parameter component to one provider, passes `check_components!` for a concrete type argument, and computes an area at run time. -- [generic_components/component_lifetime.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs) wires the lifetime-carrying component and passes `check_components!`. -- [cgp-macro-tests/tests/parser_rejections/cgp_component.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs) asserts the macro rejects a non-trait item and a trait carrying a const generic parameter. -- The `cargo-cgp` UI fixture [`acceptable/wiring/duplicate-keys/duplicate_component_name.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_component_name.rs) pins that the derived marker's `E0428` "previous definition" note falls on the provider name rather than the whole attribute — a regression test for the marker-name span (see [Failure modes](#failure-modes)). Its error class is [conflicting wiring](../../errors/wiring/conflicting-wiring.md). - -## Source - -- Entry point: `cgp_component` in [cgp-macro-lib/src/cgp_component.rs](../../../crates/macros/cgp-macro-lib/src/cgp_component.rs). -- Pipeline and AST types: [cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/), documented in [asts/cgp_component.md](../asts/cgp_component.md). -- Forwarding method bodies: the [delegated-impl helpers](../functions/derive/delegated_impls.md). -- `IsProviderFor` params tuple: [parse_is_provider_params](../functions/parse/is_provider_params.md). -- Fragment construction: [parse_internal!](../macros/parse_internal.md). diff --git a/docs/implementation/entrypoints/cgp_computer.md b/docs/implementation/entrypoints/cgp_computer.md deleted file mode 100644 index 8e4b8ca0..00000000 --- a/docs/implementation/entrypoints/cgp_computer.md +++ /dev/null @@ -1,70 +0,0 @@ -# `#[cgp_computer]` — implementation - -`#[cgp_computer]` turns a plain function into a [`Computer`](../../reference/components/computer.md) (or `AsyncComputer`) provider by emitting a `#[cgp_new_provider]` impl that calls the function and a `delegate_components!` block that promotes the rest of the handler family from that base. This document covers how the macro is built; for the accepted syntax and the full expansion a user sees, read the reference document [reference/macros/cgp_computer.md](../../reference/macros/cgp_computer.md). - -## Entry point - -The macro is the `cgp_computer` function in [cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs). Unlike the `cgp-macro-lib` macros, it is a self-contained procedural function rather than a driver over a `cgp-macro-core` AST stack: it parses the body into a `syn::ItemFn`, resolves the provider name, inspects the signature, and assembles the output with `quote!` directly. The only piece it borrows from the core crate is `to_camel_case_str`, used to derive the default provider name. - -The provider name comes from the attribute: when the attribute is empty the function name is converted to PascalCase (`add` becomes `Add`), otherwise the attribute tokens are parsed as the provider identifier. A `self` receiver on the function is rejected with a spanned error, since a handler provider has no receiver. - -## Pipeline - -There is no staged AST pipeline; the function branches on two independent axes read off the signature and emits the three items in one pass. - -- **Sync vs. async** — `fn_sig.asyncness` selects between a [`Computer`](../../reference/components/computer.md) base (the `compute` method) and an `AsyncComputer` base (an `async fn compute_async` that `.await`s the call). -- **Value vs. `Result`** — the function's return type is re-parsed as a [`MaybeResultType`](../../../crates/macros/cgp-extra-macro-lib/src/parse/maybe_result.rs), a small speculative parser that reports whether the output is written as `Result<_, E>`. This choice does not change the base impl (the `Output` associated type is the return type verbatim); it only selects which promotion bundle the `delegate_components!` block wires the rest of the family to. - -## Generated items - -The macro emits three items in order: the original function unchanged, a `#[cgp_new_provider]` impl of the base trait, and a `delegate_components!` block. The base impl collects the function's parameters into a single input tuple and destructures it back into positional `arg_0, arg_1, …` bindings inside the method body before calling the function: - -```rust -// #[cgp_computer] fn add(a: u64, b: u64) -> u64 { a + b } -#[cgp_new_provider] -impl<__Context__, __Code__> Computer<__Context__, __Code__, (u64, u64)> for Add { - type Output = u64; - - fn compute(_context: &__Context__, _code: PhantomData<__Code__>, (arg_0, arg_1): (u64, u64)) -> Self::Output { - add(arg_0, arg_1) - } -} -``` - -The function's own generics and `where` clause are cloned onto the impl, and the reserved `__Context__` and `__Code__` type parameters are appended after them, so a generic function stays generic in its provider. The `delegate_components!` block then routes every remaining handler component to a promotion bundle chosen by the two axes: - -- **sync, value** → `PromoteComputer`, over the seven non-`Computer` components. -- **sync, `Result`** → `PromoteTryComputer`, over the same seven components. -- **async, value** → `PromoteAsyncComputer`, over the smaller async set (`AsyncComputerRefComponent`, `HandlerComponent`, `HandlerRefComponent`). -- **async, `Result`** → `PromoteHandler`, over the same async set. - -The async branches delegate fewer components because the synchronous members of the family are not derivable from an async base. - -## Behavior and corner cases - -A **`self` receiver** is rejected with a spanned `syn::Error` ("Computer functions cannot have a receiver"); every other parameter is treated as part of the input tuple. Parameter *patterns* are discarded — each input is rebound positionally as `arg_i`, so a destructuring pattern in the source signature is replaced by a plain binding. - -A **reference parameter** is kept verbatim in the input-tuple type, so `fn f(value: &Value)` yields an input tuple `(&Value)`; the `PromoteComputer` bundle's `…Ref` entries then make the provider serve the `…Ref` components as well. - -The **`Result` detection is purely syntactic**. `MaybeResultType` forks the token stream and checks whether the return type's leading identifier is literally `Result`; a type-aliased result or a `Result` under a different name reads as the value case and wires `PromoteComputer` rather than `PromoteTryComputer`. - -An **omitted return type** defaults to `()`, so a unit-returning function produces a value-case `Computer` with `Output = ()`. - -## Tests - -The behavioral tests exercise the generated provider across the handler family: - -- [handlers/computer_macro.rs](../../../crates/tests/cgp-tests/tests/handlers/computer_macro.rs) — a synchronous infallible function, a `Result`-returning function, and a generic function, each called as `compute`, `try_compute`, `compute_async`, and `handle` plus their `…Ref` variants. -- [handlers/handler_macro.rs](../../../crates/tests/cgp-tests/tests/handlers/handler_macro.rs) — an `async` function (value and `Result` bodies) reached through the async promotion into `Handler`. -- [handlers/pipe_computers.rs](../../../crates/tests/cgp-tests/tests/handlers/pipe_computers.rs), [handlers/pipe_handlers.rs](../../../crates/tests/cgp-tests/tests/handlers/pipe_handlers.rs) — `#[cgp_computer]` providers composed through `PipeHandlers`. -- [dispatching/compose.rs](../../../crates/tests/cgp-tests/tests/dispatching/compose.rs) — `#[cgp_computer]` field-reader providers composed into a higher-order provider. -- [monadic_handlers/ok_monadic.rs](../../../crates/tests/cgp-tests/tests/monadic_handlers/ok_monadic.rs), [monadic_handlers/err_monadic.rs](../../../crates/tests/cgp-tests/tests/monadic_handlers/err_monadic.rs), [monadic_handlers/ok_err_monadic_trans.rs](../../../crates/tests/cgp-tests/tests/monadic_handlers/ok_err_monadic_trans.rs) — `#[cgp_computer]` providers chained through the monadic combinators. - -There is no dedicated `snapshot_cgp_computer!` macro; the macro's expansion is not pinned by a snapshot and is exercised only behaviorally. - -## Source - -- Entry point: `cgp_computer` in [cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs), forwarded from the proc-macro shim in [cgp-extra-macro/src/lib.rs](../../../crates/macros/cgp-extra-macro/src/lib.rs). -- The `Result`-versus-value split: [`MaybeResultType`](../../../crates/macros/cgp-extra-macro-lib/src/parse/maybe_result.rs). -- The emitted items lean on the base impl from [`#[cgp_new_provider]`](cgp_new_provider.md) and the wiring from [`delegate_components!`](delegate_components.md). -- The input-less sibling macro is [`#[cgp_producer]`](cgp_producer.md). diff --git a/docs/implementation/entrypoints/cgp_fn.md b/docs/implementation/entrypoints/cgp_fn.md deleted file mode 100644 index 06d3038e..00000000 --- a/docs/implementation/entrypoints/cgp_fn.md +++ /dev/null @@ -1,113 +0,0 @@ -# `#[cgp_fn]` — implementation - -`#[cgp_fn]` turns one plain Rust function into a single-implementation capability by deriving a trait carrying the method and a blanket impl of that trait over a generic context, pulling every `#[implicit]` argument out of the signature and reading it from the context's fields. This document covers how that works internally; for the accepted syntax and the complete expansion a user sees, read the reference document [reference/macros/cgp_fn.md](../../reference/macros/cgp_fn.md). - -## Entry point - -The macro is driven by the thin `cgp_fn` function in [cgp-macro-lib/src/cgp_fn.rs](../../../crates/macros/cgp-macro-lib/src/cgp_fn.rs), which parses the attribute into an optional trait-name `Ident` and the item into a `syn::ItemFn`, then runs the two-stage pipeline and emits the result. - -```rust -let item_cgp_fn = ItemCgpFn { ident, item_fn }; -let items = item_cgp_fn.preprocess()?.to_items()?; -``` - -Two failures surface at the boundary: applying the macro to a non-function item fails at `parse2::`, and a malformed attribute (anything other than a single optional identifier) fails at `parse2::>`. All real logic lives in `cgp-macro-core`. - -## Pipeline - -The macro moves through two stages, each a method on the AST type the previous one produced; the [`cgp_fn` AST stack](../asts/cgp_fn.md) documents those types in full. - -- **preprocess** normalizes the function into the pieces the emit stage needs: it derives the trait name (the attribute identifier, or the function name converted to PascalCase), extracts the `#[implicit]` arguments out of the signature and prepends their field-reading bindings to the body, splits the companion attributes (`#[uses]`, `#[extend]`, `#[use_type]`, and the rest) out of the raw attribute list, and moves the function's generics aside. -- **to_items** renders the two output items — the trait carrying the method signature, then the blanket impl over `__Context__` carrying the body. - -## Generated items - -`#[cgp_fn]` emits exactly two items in a fixed order: the trait with the method, and the blanket impl of that trait for the reserved context type `__Context__`. There is no provider struct, no component marker, and no wiring — the method becomes available on every context that satisfies the impl's `where` clause. - -The interesting transform is what happens to an `#[implicit]` argument. The argument is removed from the method signature entirely, its field is required as a `HasField` bound on the impl, and a `let` binding that reads the field is spliced onto the front of the body so the original body sees the name unchanged: - -```rust -// input -#[cgp_fn] -pub fn greet(&self, #[implicit] name: &str) { - println!("Hello, {}!", name); -} - -// derived trait — the implicit argument is gone from the signature -pub trait Greet { - fn greet(&self); -} - -// derived blanket impl — the field is a bound, the binding is prepended -impl<__Context__> Greet for __Context__ -where - Self: HasField, -{ - fn greet(&self) { - let name: &str = self.get_field(PhantomData::).as_str(); - println!("Hello, {}!", name); - } -} -``` - -The conversion applied to each binding is chosen by the argument's type, following the same field-mode rules the getter macros use: a `&str` argument reads a `String` field and appends `.as_str()`, an owned value — a path type, tuple, or array — appends `.clone()`, an `Option<&T>` reads an `Option` field and appends `.as_ref()`, an `Option<&str>` reads an `Option` field and appends `.as_deref()`, an `&[T]` reads an `AsRef<[T]>` field and appends `.as_ref()`, and a plain `&T` is taken by reference with no conversion. Each reference mode has a mutable mirror selected by a `&mut` in the argument's type: a `&mut T` reads through `HasFieldMut`/`get_field_mut`; a `&mut [T]` reads an `AsMut<[T]>` field and appends `.as_mut()`; an `Option<&mut T>` reads an `Option` field and appends `.as_mut()`; an `Option<&mut str>` appends `.as_deref_mut()`. Every mutable read requires a `&mut self` receiver, while every immutable argument reads through `HasField`/`get_field` regardless of the receiver. The slice and option modes are keyed off the *argument's own* reference, not the receiver's, so an immutable `&[T]` keeps its shared `AsRef<[T]>` bound even under a `&mut self` receiver rather than being forced mutable. These modes are shared with `#[cgp_auto_getter]` and `#[cgp_getter]` through the [field-parsing helpers](../asts/cgp_getter.md); the difference is only where the read lands — a prepended `let` in the body here, a getter-method body there. - -## Behavior and corner cases - -**Generic parameters and the `where` clause split deliberately.** Every generic parameter in the function's `<...>` list is copied onto both the generated trait and the impl, while the function's own `where` clause is treated as an impl-side dependency and lands only on the impl, hidden from the trait interface. The implicit `HasField` bounds are always appended last, after any attribute-contributed predicates, so the impl's `where` clause reads user-declared bounds first and field bounds last. - -**The companion attributes are layered into the same two items.** `#[uses(Trait)]` and `#[extend(Trait)]` each push a `Self: Trait` predicate onto the impl; `#[extend(Trait)]` additionally makes `Trait` a supertrait of the generated trait, `#[extend_where(...)]` adds predicates to the trait's own `where` clause, `#[impl_generics(...)]` inserts parameters into the impl generics only, and `#[use_type]`/`#[use_provider]` transform the trait and impl as documented on their own pages. Any attribute the parser does not recognize (for example `#[async_trait]` or `#[allow(...)]`) is preserved as a raw attribute and re-attached to both the trait and the impl. - -**The visibility is moved, not copied.** `preprocess` takes the function's visibility off the inner `ItemFn` and re-applies it to the generated trait, so the emitted method inside the trait is always inherited-visibility while the trait itself carries the `pub` the user wrote. - -**A malformed `#[implicit]` attribute is rejected at extraction.** `#[implicit]` is recognized only as a bare `Meta::Path`; the extraction pass additionally checks for an `implicit`-named attribute in any other form (`#[implicit(...)]` or `#[implicit = ...]`) and returns a spanned error, rather than leaving the stray attribute on the parameter to surface downstream as an obscure `cannot find attribute 'implicit'` error. - -**A mutable implicit argument must be the only implicit argument.** The `field_mut` of each argument follows the argument's own type — set from a `&mut` in the type, whether the outer reference of a `&mut T`/`&mut [T]` or the inner reference of an `Option<&mut T>`, not from the receiver — so a mutable argument reads through `get_field_mut` while every immutable argument reads through `get_field`, even under a `&mut self` receiver. Because a `get_field_mut` read borrows the whole context exclusively for the rest of the body, a mutable implicit cannot coexist with any other implicit read; extraction rejects the combination (`has_mutable && count > 1`) rather than emit a blanket impl that fails to borrow-check. Any number of purely immutable implicits, by contrast, are shared borrows and combine freely. A mutable argument additionally requires the `&mut self` receiver (checked in `parse_field_type`), and a `mut` *pattern* on any implicit argument is rejected outright. These checks are enforced during implicit-argument extraction. - -## Known issues - -`#[cgp_fn]` does not support generics on the desugared *method* itself — generic parameters are only ever lifted onto the trait and impl. A method-level generic is silently treated as a trait/impl generic rather than rejected, which is the intended limitation rather than a bug: method-level generics are considered an advanced case better written as an explicit blanket impl or a [`#[cgp_component]`](../../reference/macros/cgp_component.md) provider. - -## Snapshots - -Every `snapshot_cgp_fn!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint: - -- [implicit_arguments/cgp_fn_greet.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_greet.rs) — the canonical plain case: one `#[implicit]` `&str` argument dropped from the signature and read via `HasField` with `.as_str()` applied. -- [implicit_arguments/cgp_fn_custom_trait_name.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_custom_trait_name.rs) — `#[cgp_fn(CanCalculateRectangleArea)]` overrides the generated trait name; two owned `f64` implicits each `.clone()`d. -- [implicit_arguments/cgp_fn_mutable.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mutable.rs) — `&mut self` with a mutable implicit argument, reading through `HasFieldMut`/`get_field_mut`. -- [implicit_arguments/cgp_fn_mut_self_immutable.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_self_immutable.rs) — a `&mut self` receiver with two *immutable* implicit arguments, showing they read through `HasField`/`get_field` (the access mode follows the argument, not the receiver) and that several immutable implicits may share a `&mut self` receiver. -- [implicit_arguments/cgp_fn_slice.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_slice.rs) — an immutable `&[u8]` implicit under a `&mut self` receiver, keeping its `Value: AsRef<[u8]> + 'static` bound and `.as_ref()` read because the slice mode follows the argument's own reference, not the receiver's; also mixes an implicit with a plain explicit argument. -- [implicit_arguments/cgp_fn_mut_slice.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_slice.rs) — a mutable `&mut [u8]` implicit, reading through `HasFieldMut` with a `Value: AsMut<[u8]> + 'static` bound and a `get_field_mut(...).as_mut()` read, the mutable mirror of the shared-slice case. -- [implicit_arguments/cgp_fn_option_mut.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_option_mut.rs) — a mutable `Option<&mut u8>` implicit, reading an `Option` field through `HasFieldMut` with a `get_field_mut(...).as_mut()` read that yields `Option<&mut u8>`. -- [implicit_arguments/cgp_fn_owned_tuple.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_owned_tuple.rs) — an owned, non-path implicit (a tuple) read by value with `.clone()` and a `Value = (f64, f64)` bound, showing owned types beyond path types are accepted. -- [implicit_arguments/cgp_fn_option_str.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_option_str.rs) — an `Option<&str>` implicit backed by an `Option` field and read with `.as_deref()`, composing the `&str` and option modes. -- [implicit_arguments/cgp_fn_calling_fn.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_calling_fn.rs) — one `#[cgp_fn]` capability depending on another through an explicit `where Self:` bound. -- [implicit_arguments/cgp_fn_multi_and_use_type.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs) — explicit and implicit arguments mixed, generic type parameters lifted onto the trait, `#[async_trait]` preserved as a raw attribute, and `#[use_type]` importing and renaming abstract types. -- [async_and_send/cgp_fn_async.rs](../../../crates/tests/cgp-tests/tests/async_and_send/cgp_fn_async.rs) — the canonical async expansion, an `async fn` combined with `#[async_trait]`. -- [generic_components/fn_generic_param.rs](../../../crates/tests/cgp-tests/tests/generic_components/fn_generic_param.rs) — a function generic over a type parameter, showing the parameter moved onto both trait and impl. -- [generic_components/fn_impl_generics.rs](../../../crates/tests/cgp-tests/tests/generic_components/fn_impl_generics.rs) — `#[impl_generics(...)]` adding a generic parameter to the impl only, not the trait. -- [impl_side_dependencies/fn_extend.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_extend.rs) — `#[extend(...)]` adding a supertrait bound to the generated trait. -- [impl_side_dependencies/fn_uses.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs) — `#[uses(...)]` importing a `Self` trait bound as an impl-side dependency. -- [higher_order_providers/use_provider_fn.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_fn.rs) — `#[use_provider]` on a `#[cgp_fn]`, borrowing another provider's behavior. -- [abstract_types/use_type_fn_alias.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs), [use_type_fn_equality.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs), [use_type_fn_extend.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_extend.rs), [use_type_fn_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs), [use_type_fn_foreign_equality.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs), [use_type_fn_nested_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs), [use_type_fn_deep_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_deep_foreign.rs), [use_type_fn_equality_cross_trait.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs), [use_type_fn_foreign_equality_cross_trait.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs) — the `#[use_type]` variants (local alias, type-equality bound, `#[extend]` form, foreign generic parameter, a three-hop foreign chain, and their combinations). The `#[extend_where(...)]` attribute's effect on the trait's own `where` clause is pinned by [use_type_fn_nested_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs), where it adds a `Scalar: Copy` bound (rewritten to the two-hop path) alongside the `#[use_type]` imports. - -## Tests - -Because `#[cgp_fn]` emits a blanket impl, its snapshot tests double as behavioral checks — the snapshot file also derives a concrete context and asserts it implements the generated trait, so a compile of the file is itself the wiring check. The runtime assertions worth calling out: - -- [implicit_arguments/cgp_fn_greet.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_greet.rs) proves any context with a `name: String` field implements the generated `Greet` trait via a `CheckPerson` bound. -- [implicit_arguments/cgp_fn_calling_fn.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_calling_fn.rs) confirms one capability calling another resolves through both blanket impls at run time. -- [impl_side_dependencies/fn_uses.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs) and [impl_side_dependencies/fn_extend.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_extend.rs) exercise the two ways of stating a dependency and confirm the resulting bounds are satisfiable. - -The failure cases pin the inputs `#[cgp_fn]` refuses during expansion, each asserting the entrypoint returns `Err`: - -- [parser_rejections/cgp_fn.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_fn.rs) covers an implicit argument on a function with no `self` receiver, a `mut` binding pattern on an implicit argument, a `&mut` implicit argument that is not the sole implicit (which would borrow the context mutably and again at once), a malformed `#[implicit]` attribute carrying arguments (`#[implicit(...)]` or `#[implicit = ...]`), and the two mutable-reference forms under a `&self` receiver — a `&mut [T]` slice and an `Option<&mut T>` — each of which reads through `get_field_mut` and so requires `&mut self`. - -## Source - -- Entry point: `cgp_fn` in [cgp-macro-lib/src/cgp_fn.rs](../../../crates/macros/cgp-macro-lib/src/cgp_fn.rs). -- Pipeline and its AST types: [cgp-macro-core/src/types/cgp_fn/](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/), documented in [asts/cgp_fn.md](../asts/cgp_fn.md). -- Implicit-argument extraction and field-reading bindings: [cgp-macro-core/src/functions/implicits/](../../../crates/macros/cgp-macro-core/src/functions/implicits/). -- Field-mode helpers: [cgp-macro-core/src/functions/field/](../../../crates/macros/cgp-macro-core/src/functions/field/). -- Companion-attribute parsing: [cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs). -- Fragment construction: [parse_internal!](../macros/parse_internal.md). diff --git a/docs/implementation/entrypoints/cgp_getter.md b/docs/implementation/entrypoints/cgp_getter.md deleted file mode 100644 index f0c7fa30..00000000 --- a/docs/implementation/entrypoints/cgp_getter.md +++ /dev/null @@ -1,95 +0,0 @@ -# `#[cgp_getter]` — implementation - -`#[cgp_getter]` builds a full getter component — everything `#[cgp_component]` produces — and then appends three field-reading provider impls (`UseFields`, `UseField`, `WithProvider`) so a context can bind each getter to a source field by wiring rather than by method name. This document covers how that works internally; for the accepted syntax and the complete expansion a user sees, read the reference document [reference/macros/cgp_getter.md](../../reference/macros/cgp_getter.md). - -## Entry point - -The macro is driven by the `cgp_getter` function in [cgp-macro-lib/src/cgp_getter.rs](../../../crates/macros/cgp-macro-lib/src/cgp_getter.rs), which parses the attribute into `CgpComponentRawArgs` and the item into a `syn::ItemTrait`, then reuses the `#[cgp_component]` pipeline before layering the getter-specific impls on top. - -```rust -let evaluated = ItemCgpComponent { args, item_trait }.preprocess()?.eval()?; -let item_getter = ItemCgpGetter::try_from(evaluated)?; -let items = item_getter.to_items()?; -``` - -Before building the component, the entry function derives the default provider name specific to getters: if the user gave no provider identifier and the trait name begins with `Has`, the provider defaults to the remainder plus `Getter` (so `HasName` yields `NameGetter`, component `NameGetterComponent`). Only after that does it hand off to the shared component args conversion. Applying the macro to a non-trait item fails at `parse2::`, and a malformed attribute is rejected by the `CgpComponentRawArgs` parser. - -## Pipeline - -The macro runs the entire `#[cgp_component]` pipeline and then a getter-specific emit stage; the getter-side AST types are documented in the [`cgp_getter` AST stack](../asts/cgp_getter.md) and the component stages in the [`cgp_component` AST stack](../asts/cgp_component.md). - -- **preprocess → eval** are the `#[cgp_component]` stages unchanged: they strip the CGP modifier attributes off the trait, then derive the provider trait, the two routing blanket impls, and the component marker, producing an `EvaluatedCgpComponent`. -- **parse getter fields** happens in `ItemCgpGetter::try_from`, which parses each method of the consumer trait into a `GetterField` (its field name, field type, return type, receiver mode, and field mode) and captures an optional single associated return type. -- **to_items** emits the component's own items first, then appends the three getter provider impls. - -## Generated items - -`#[cgp_getter]` emits the five core `#[cgp_component]` items plus the standard `UseContext`/`RedirectLookup` provider impls, and then adds three more provider impls that all read from `HasField`. The reference document shows the full expansion; the point worth understanding here is what distinguishes the three added impls. - -- **`UseFields`** implements the getter by reading the field named after the method — the same behavior a `#[cgp_auto_getter]` blanket impl gives, but expressed as a provider a context can wire. -- **`UseField<__Tag__>`** implements the getter by reading the field named `__Tag__`, a *free* generic parameter, so wiring `UseField` makes the getter read `first_name` regardless of the method name. This is the whole reason `#[cgp_getter]` exists rather than `#[cgp_auto_getter]`. -- **`WithProvider<__Provider__>`** implements the getter by delegating to an inner `FieldGetter`/`MutFieldGetter` provider, so the field access itself can be supplied by another provider. - -Each of these reads the field through the same getter-method body the auto-getter uses — a `get_field(PhantomData::)` call with the field-mode conversion appended. The `UseField` impl keys on `__Tag__`: - -```rust -// for `fn foo(&self) -> &str` reading a String field -impl<__Context__, __Tag__> FooGetter<__Context__> for UseField<__Tag__> -where - __Context__: HasField<__Tag__, Value = String>, -{ - fn foo(__context__: &__Context__) -> &str { - __context__.get_field(PhantomData::<__Tag__>).as_str() - } -} -``` - -Every provider-trait impl is paired with a matching `IsProviderFor` impl carrying the same bounds, exactly as the component macro pairs its own impls, so wiring failures still name the missing `HasField` bound. - -## Behavior and corner cases - -**The `UseField` and `WithProvider` impls are only emitted for a single-getter trait.** Both `to_use_field_impl` and `to_with_provider_impl` return `None` when the trait declares more than one getter method, because a per-field tag or per-field inner provider is meaningless once several fields are in play; the `UseFields` impl, keyed by method name, is always emitted. - -**A getter can read a field of a type other than the context.** When a method takes a typed receiver rather than `&self` — `fn foo_bar(foo: &Self::Foo) -> &Self::Bar` — the receiver's `Self` is rewritten to the context and the generated impls read the field out of that receiver type instead of the context. The provider impls then bound that receiver type, not the context, with the `HasField` requirement. - -**A single associated return type is supported and inferred from the field.** A getter trait may declare one `type Name;` used as the return type; the associated type is added as an extra generic parameter to each provider impl, set to itself via `type Name = Name;`, and any bound on it (for example `Name: Display`) is carried onto the impl with `Self::Name` rewritten to the parameter. More than one associated type, or an associated type alongside more than one method, is rejected during field parsing. - -**The return-type shorthands are shared with the auto-getter.** The `&str`-reads-`String`, `Option<&T>`-reads-`Option`, `&[T]`-reads-`AsRef<[T]>`, `MRef<'_, T>`, and owned-`.clone()` conversions, along with their mutable mirrors under a `&mut self` receiver (`&mut [T]`-reads-`AsMut<[T]>`, `Option<&mut T>` via `.as_mut()`), all come from the shared field-mode parsing, so `#[cgp_getter]` and [`#[cgp_auto_getter]`](cgp_auto_getter.md) treat a given signature identically — they differ only in the items they emit around the shared getter-method body. - -## Snapshots - -Every `snapshot_cgp_getter!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint: - -- [getters/string.rs](../../../crates/tests/cgp-tests/tests/getters/string.rs) — the canonical full expansion: a `&str` getter over a `String` field, showing all five component items plus the `UseContext`, `RedirectLookup`, `UseFields`, `UseField`, and `WithProvider` impls. -- [getters/clone.rs](../../../crates/tests/cgp-tests/tests/getters/clone.rs) — an owned return `.clone()`d out by value. -- [getters/mref.rs](../../../crates/tests/cgp-tests/tests/getters/mref.rs) — an `MRef<'_, String>` return wrapping the borrow in `MRef::Ref`. -- [getters/option.rs](../../../crates/tests/cgp-tests/tests/getters/option.rs) — an `Option<&String>` return reading an `Option` field via `.as_ref()`. -- [getters/slice.rs](../../../crates/tests/cgp-tests/tests/getters/slice.rs) — a `&[u8]` return reading any `AsRef<[u8]> + 'static` field via `.as_ref()`. -- [getters/mut_getter.rs](../../../crates/tests/cgp-tests/tests/getters/mut_getter.rs) — a `&mut self` getter returning `&mut u32`: the `UseFields` and `UseField` impls read through `HasFieldMut`/`get_field_mut`, and the `WithProvider` impl delegates to a `MutFieldGetter` rather than a `FieldGetter`. -- [getters/mut_slice.rs](../../../crates/tests/cgp-tests/tests/getters/mut_slice.rs) — a `&mut self` getter returning `&mut [u32]`: the mutable-slice path, where the `UseField` and `MutFieldGetter` bounds are the `AsMut<[u32]> + 'static` mirror of `slice.rs`'s `AsRef<[u32]>`. -- [getters/non_self.rs](../../../crates/tests/cgp-tests/tests/getters/non_self.rs) — a non-`self` getter reading a field out of another type (`&Self::Foo`). -- [getters/string_custom_name.rs](../../../crates/tests/cgp-tests/tests/getters/string_custom_name.rs) — `#[cgp_getter(GetString)]` overriding the provider name (and component). -- [getters/string_custom_spec.rs](../../../crates/tests/cgp-tests/tests/getters/string_custom_spec.rs) — the brace-spec form overriding provider and component names independently. -- [getters/assoc_type_getter.rs](../../../crates/tests/cgp-tests/tests/getters/assoc_type_getter.rs) — a local associated return type, showing the associated-type parameter threaded through every provider impl. -- [getters/assoc_type_self_referential.rs](../../../crates/tests/cgp-tests/tests/getters/assoc_type_self_referential.rs) — a self-referential associated-type bound with the source field bound by wiring to a differently-named field. -- [getters/abstract_type_extend.rs](../../../crates/tests/cgp-tests/tests/getters/abstract_type_extend.rs), [getters/abstract_type_use_type.rs](../../../crates/tests/cgp-tests/tests/getters/abstract_type_use_type.rs) — getters whose return type is an abstract type imported from another component via `#[extend]` and `#[use_type]`; each file pins both the auto and the full getter variant. - -The `UseDelegate`-table dispatch form of a getter is snapshotted separately in [dispatching/use_delegate_getter.rs](../../../crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs), which the dispatching concept owns rather than this entrypoint. One variant has no snapshot yet: a getter component carrying its own generic parameter (distinct from the auto-getter's `auto_getter_generic.rs`). - -The behavioral tests also assert the mutable full-getter resolves; see the Tests section. - -## Tests - -The getter snapshot files also wire concrete contexts and assert the getters resolve, so the same files carry the behavioral checks: - -- [getters/string.rs](../../../crates/tests/cgp-tests/tests/getters/string.rs) and [getters/assoc_type_self_referential.rs](../../../crates/tests/cgp-tests/tests/getters/assoc_type_self_referential.rs) confirm that wiring `UseField` makes the getter read a field whose name differs from the method. -- [getters/clone.rs](../../../crates/tests/cgp-tests/tests/getters/clone.rs), [getters/mref.rs](../../../crates/tests/cgp-tests/tests/getters/mref.rs), [getters/option.rs](../../../crates/tests/cgp-tests/tests/getters/option.rs), and [getters/slice.rs](../../../crates/tests/cgp-tests/tests/getters/slice.rs) verify each return-type shorthand at run time. -- [getters/mut_getter.rs](../../../crates/tests/cgp-tests/tests/getters/mut_getter.rs) mutates the context through a `&mut self` getter wired to `UseField`, confirming the mutable provider impls resolve; [getters/mut_slice.rs](../../../crates/tests/cgp-tests/tests/getters/mut_slice.rs) does the same for a `&mut [u32]` getter over a `Vec` field, exercising the `AsMut<[T]>` bound. - -## Source - -- Entry point: `cgp_getter` in [cgp-macro-lib/src/cgp_getter.rs](../../../crates/macros/cgp-macro-lib/src/cgp_getter.rs), which derives the `Has…` → `…Getter` default name and reuses the component pipeline. -- Getter-specific stack: [cgp-macro-core/src/types/cgp_getter/](../../../crates/macros/cgp-macro-core/src/types/cgp_getter/), documented in [asts/cgp_getter.md](../asts/cgp_getter.md): `item.rs` assembles the items, `to_use_fields_impl.rs`/`use_field.rs`/`with_provider.rs` build the three added provider impls. -- Component stages it reuses: [cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/), documented in [asts/cgp_component.md](../asts/cgp_component.md). -- Getter-method parsing and the return-type shorthands, shared with `#[cgp_auto_getter]`: [cgp-macro-core/src/functions/getter/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/getter/parse.rs) and [cgp-macro-core/src/functions/field/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/field/parse.rs). -- Fragment construction: [parse_internal!](../macros/parse_internal.md). diff --git a/docs/implementation/entrypoints/cgp_impl.md b/docs/implementation/entrypoints/cgp_impl.md deleted file mode 100644 index f687076e..00000000 --- a/docs/implementation/entrypoints/cgp_impl.md +++ /dev/null @@ -1,114 +0,0 @@ -# `#[cgp_impl]` — implementation - -`#[cgp_impl]` lets a provider be written in consumer-trait clothing — an `impl Trait for Context` block that keeps `self`/`Self` — and lowers it into the provider-trait form that CGP requires, then hands that form to the same machinery [`#[cgp_provider]`](cgp_provider.md) drives. This document covers how the lowering works internally; for the accepted syntax and the full before/after expansion, read the reference document [reference/macros/cgp_impl.md](../../reference/macros/cgp_impl.md). - -## Entry point - -The macro is driven by the `cgp_impl` function in [cgp-macro-lib/src/cgp_impl.rs](../../../crates/macros/cgp-macro-lib/src/cgp_impl.rs). It parses the attribute into an `ImplArgs` (the optional `new` keyword, the provider type, and an optional `: ComponentType` override) and the item into a `syn::ItemImpl`, then runs the two-step lowering and emits the result: - -```rust -let item_cgp_impl = ItemCgpImpl { args, item_impl }; -let lowered = item_cgp_impl.lower()?; -let bare_impls = lowered.lower()?; -``` - -The first `lower` produces a `LoweredCgpImpl`; the second turns that into either a raw provider impl (fed through `#[cgp_provider]`) or an untouched consumer impl. The entry function emits `bare_impls` followed by any `default_impls` the block's `#[default_impl]` attributes generated. A malformed attribute is rejected while parsing `ImplArgs`, and applying the macro to a non-`impl` item fails at `syn::parse2::`. - -## Pipeline - -The macro moves through two lowering stages, each a method on the AST type the previous one produced; the [`cgp_impl` AST stack](../asts/cgp_impl.md) documents those types in full. - -- **`ItemCgpImpl::lower`** processes the companion attributes and normalizes the impl header. It strips the CGP modifier attributes (`#[implicit]` arguments, `#[uses]`, `#[use_type]`, `#[use_provider]`, `#[default_impl]`) off the block, folds each into the impl generics or method bodies, and — when the `for Context` clause is omitted — inserts the reserved `__Context__` parameter so later stages always see an explicit context. It records the provider trait path and the context type on the resulting `LoweredCgpImpl`. -- **`LoweredCgpImpl::lower`** performs the consumer-to-provider rewrite: it swaps the provider type into the `Self` position, moves the context to the leading argument of the provider trait, rewrites `self`/`Self` with the [`replace_self` visitors](../asts/cgp_impl.md#the-replace_self-visitors), and hands the result to [`#[cgp_provider]`](cgp_provider.md)'s `ItemCgpProvider::lower`. The `#[cgp_impl(Self)]` special case skips the rewrite entirely and passes the block through unchanged. - -## Generated items - -For a normal provider, the macro emits exactly what `#[cgp_provider]` emits — the provider impl, its derived `IsProviderFor` impl, and (when `new` is set) the provider struct — plus one delegation impl per `#[default_impl]` attribute. The interesting work is upstream of that handoff: turning consumer-style syntax into the provider impl. - -The lowering swaps the `Self` type to the provider, adds the context as the provider trait's leading type argument, and rewrites the receiver into a context parameter. A method that took `&self` becomes a static method taking the context by a snake-cased, double-underscored name derived from the context type: - -```rust -// consumer-style input -#[cgp_impl(new ValueToString)] -impl FooProvider for Context { - fn foo(&self, value: u32) -> String { value.to_string() } -} - -// lowered provider impl handed to #[cgp_provider] -impl FooProvider for ValueToString { - fn foo(__context__: &Context, value: u32) -> String { value.to_string() } -} -``` - -When the `for Context` clause is omitted, the inserted parameter is `__Context__`, so the same rewrite produces `__context__: &__Context__`. Both the explicit `Context` and the default `__Context__` snake-case to the same `__context__` receiver identifier. - -## Behavior and corner cases - -The **receiver identifier** is computed from the context type: if the context type is a bare identifier it is snake-cased and the result used as the parameter name, and any context type that is not a plain identifier falls back to the literal `__context__`. `Context` and `__Context__` both yield `__context__`. Every `self` in a body is rewritten to that identifier, every `Self` type to the context type, via the three `replace_self` visitors run in sequence. - -The rewrite is **scoped to the block's own methods and stops at any nested item.** A `struct`, `impl`, `trait`, or `fn` defined *inside* a method body introduces its own `self`/`Self` scope that names that nested item — a Rust nested item cannot see the enclosing impl's `Self` or receiver — so the visitors must not rewrite it. All three `replace_self` visitors override `visit_item_mut` to a no-op, leaving a nested item untouched while still descending into closures and other expressions, which *do* capture the outer `self`. A local `impl Display for Wrapper { fn fmt(&self, …) { … self.0 … } }` inside a provider method therefore keeps its own `&self` receiver and `self.0` access. (The `visit_item_mut` guard fires only for block-nested items, since a trait's or impl's associated items are `TraitItem`/`ImplItem`, not `Item`, so a provider method or associated type declared at the block level is still rewritten.) - -Inside a **macro body** the rewrite is token-level, because `VisitMut` cannot see through a `macro!( … )`. The type visitor still skips a local associated type (`Self::Output` where `Output` is declared in the block), and the value visitor still distinguishes the two meanings of `self`: a bare `self` value becomes the context, but a `self::` module path is left intact, since a value `self` is never followed by `::`. The token-level pass cannot reason about scope, so a nested item written *inside* a macro invocation does not stop the rewrite the way a nested item at the AST level does — its `self`/`Self` are still rewritten. - -A **`for Context` clause is optional**, and omitting it is the idiomatic form. When present, the `Self` type of the block *is* the context and the trait path is the provider trait; when absent, `ItemCgpImpl::lower` treats the block's `Self` type as the provider trait path and inserts `__Context__` at the front of the impl generics. - -**Local associated types are preserved by name.** A `type Output = …` the block declares itself is collected before the rewrite so the `replace_self` type visitor leaves `Self::Output` alone rather than rewriting it to the context — only imported abstract types (`Self::Error` from `#[use_type]`) and receiver `self`/`Self` are rewritten. - -The **companion attributes** are applied in `ItemCgpImpl::lower` before the provider rewrite: `#[implicit]` parameters are extracted from each signature and turned into `HasField` bounds on the context (and reads in the body), `#[uses(...)]` and `#[use_provider(...)]` add `Self` trait bounds to the impl generics, `#[use_type(...)]` imports an abstract type and rewrites its occurrences, and each `#[default_impl]` becomes a separate `DelegateComponent`-style impl emitted alongside the provider. - -The **`#[cgp_impl(Self)]` passthrough** bypasses the whole rewrite: when the provider type is the literal `Self`, `LoweredCgpImpl::lower` returns the original `impl` block unchanged as an ordinary consumer-trait impl, so companion attributes still apply while the body keeps its `self` receiver. This form requires the `for Context` clause; omitting it is rejected with a spanned "Expected context type to be specified" error. - -## Error spans - -Because the macro starts from the user's own `syn::ItemImpl` and rewrites it in place, the provider impl and its derived `IsProviderFor` impl keep the user's spans on their `impl` keyword, generics, self type, and body — so a coherence conflict (`E0119`) between two providers already underlines the offending `#[cgp_impl]` block rather than the macro name, exactly as two hand-written impls would. Two tokens are synthesized rather than cloned, and both are aimed at what the user wrote so they cannot leak the `call_site` span: `to_raw_item_impl` gives the provider impl's `for` token the original `for` token's span (explicit `impl Trait for Context` form) or the provider trait path's span (bare `impl Trait` form), and the [`IsProviderFor` derivation](../asts/cgp_provider.md#itemproviderimpl-and-the-isproviderfor-derivation) reuses that same `for` token for its own header. - -The one token in the derived `IsProviderFor` impl that stays on `call_site` is the derived component reference, and deliberately so. It anchors no caret, so a narrower span gains nothing, and giving it the provider trait's span would make go-to-definition on the provider trait a user wrote also offer the component struct — because rust-analyzer maps a source token to its expansion by source range. The [`IsProviderFor` derivation](../asts/cgp_provider.md#itemproviderimpl-and-the-isproviderfor-derivation) keeps it on `call_site`, off every user token's range. - -The one wholly-synthesized item is each `#[default_impl]` delegation impl, built entirely from quasi-quoted tokens whose `impl`/`{ … }` boundary carries `call_site`. `DefaultImplAttribute::to_item_impl` re-spans that boundary onto the user-written key token — with [`override_item_span`](delegate_components.md#error-spans), the same helper the `delegate_components!` impls use — so a conflict between two default impls for the same key points at the key inside `#[default_impl(Key in …)]` rather than at the whole `#[cgp_impl]` attribute. Only the boundary moves; the interior tokens — the provider type, a per-entry generic, each synthesized reference — keep their spans, so the user's tokens stay navigable in an IDE. - -## Failure modes - -Both failures below are ones `#[cgp_impl]` defers to the compiler, and both are the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class; what this document pins is where each caret lands. Each is covered by a `cargo-cgp` UI fixture, linked per case below. - -A **duplicate provider name** — two `#[cgp_impl(new Foo)]` blocks — declares `pub struct Foo;` twice (`E0428`) and emits two conflicting provider impls (`E0119`). The `E0428` carets land on the `Foo` inside each `#[cgp_impl(new …)]` and the `E0119` carets on each provider `impl` block, per [Error spans](#error-spans). Pinned by [`acceptable/wiring/duplicate-keys/duplicate_provider_name.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_provider_name.rs). - -A **duplicate `#[default_impl]` key** — two `#[cgp_impl]` blocks each registering the same key as a per-type default in the same namespace — emits two conflicting delegation impls and fails with `E0119`. The carets land on the `Key` inside each `#[default_impl(Key in …)]` per [Error spans](#error-spans). Pinned by [`acceptable/wiring/namespace-paths/duplicate_default_impl.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/duplicate_default_impl.rs). - -## Known issues - -The `#[cgp_impl(Self)]` form requires an explicit `for Context` clause and errors cleanly when it is missing. In this form the `new` keyword and the `: ComponentType` override are silently ignored rather than rejected: the `Bare` branch returns the `impl` block untouched and never consults `args.new` or `args.component_type`, so `#[cgp_impl(new Self)]` and `#[cgp_impl(Self: SomeComponent)]` parse and compile but have the same effect as a plain `#[cgp_impl(Self)]`. This is harmless — neither option is meaningful for a direct consumer-trait impl — but a stricter parser would reject them. Beyond this, the macro has no known limitations specific to it apart from those inherited from [`#[cgp_provider]`](cgp_provider.md) (see its Known issues). - -## Snapshots - -Every `snapshot_cgp_impl!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint: - -- [basic_delegation/provider_macro.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/provider_macro.rs) — the canonical plain expansion: `#[cgp_impl(new ValueToString)]` on an explicit `impl FooProvider for Context`, showing the `Self`-to-provider swap, the leading-context insertion, and `&self` becoming `__context__: &Context`. -- [implicit_arguments/cgp_impl_implicit.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit.rs) — `#[implicit]` arguments dropped from the signature and turned into `HasField` reads, with the implicit `__Context__` inserted (the `for` clause omitted). -- [higher_order_providers/use_provider_impl.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs) — a generic higher-order provider `ScaledArea` with `#[use_provider]` completing the inner provider's bound. -- [namespaces/default_impls.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls.rs) — several `#[cgp_impl]` blocks (`ShowString`, `ShowWithDisplay`, `ShowU32`) providing a generic component, exercising the `#[default_impl]` companion attribute alongside the provider rewrite. -- [basic_delegation/provider_component_override.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/provider_component_override.rs) — the `: ComponentType` override: `#[cgp_impl(new FortyTwo: HasFooComponent)]` targets a component whose marker name (`HasFooComponent`) is not the derived `FooProviderComponent`, so the `IsProviderFor` impl names the overriding component rather than the default. - -The `#[cgp_impl(Self)]` bare-impl passthrough has no snapshot yet. - -## Tests - -The behavioral tests confirm the lowered wiring works: - -- [basic_delegation/provider_macro.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/provider_macro.rs) wires `FooProviderComponent` to the generated `ValueToString` and checks the call resolves at run time. -- [implicit_arguments/cgp_impl_implicit.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit.rs) wires the implicit-argument provider through `delegate_and_check_components!` and confirms the field reads compute the area. -- [higher_order_providers/use_provider_impl.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs) wires the scaled higher-order provider onto a context and runs it. -- [basic_delegation/impl_self.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/impl_self.rs) exercises the `#[cgp_impl(Self)]` passthrough: a consumer trait implemented directly on a concrete context, forwarding to a provider via `#[use_provider]`. -- [basic_delegation/self_in_macro.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/self_in_macro.rs) confirms the token-level `self` rewrite inside a macro body distinguishes a bare `self` value (rewritten) from a `self::` module path (left intact). -- [basic_delegation/self_in_nested_item.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/self_in_nested_item.rs) confirms the rewrite stops at a nested item: a local `impl Display for Wrapper` inside a provider method keeps its own `&self` receiver and `self.0` access while the outer `self.name()` is still rewritten to the context. -- [basic_delegation/provider_component_override.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/provider_component_override.rs) wires the overriding component (`HasFooComponent`) to the generated `FortyTwo` and confirms `App` implements `CanDoFoo` — proving the `: ComponentType` override is honored rather than ignored in favor of the derived name. - -The rejection cases confirm the macro refuses malformed input: - -- [parser_rejections/cgp_impl.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_impl.rs) checks that an empty attribute (no provider name), a `#[cgp_impl(Self)]` block missing its `for` clause, and application to a non-`impl` item are each rejected. - -## Source - -- Entry point: `cgp_impl` in [cgp-macro-lib/src/cgp_impl.rs](../../../crates/macros/cgp-macro-lib/src/cgp_impl.rs). -- Lowering stages and their AST types: [cgp-macro-core/src/types/cgp_impl/](../../../crates/macros/cgp-macro-core/src/types/cgp_impl/), documented in [asts/cgp_impl.md](../asts/cgp_impl.md). -- `self`/`Self` rewriting: the `replace_self` visitors in [cgp-macro-core/src/visitors/replace_self/](../../../crates/macros/cgp-macro-core/src/visitors/replace_self/). -- Handoff target — the provider impl and its `IsProviderFor` derivation: documented in [entrypoints/cgp_provider.md](cgp_provider.md) and [asts/cgp_provider.md](../asts/cgp_provider.md). diff --git a/docs/implementation/entrypoints/cgp_namespace.md b/docs/implementation/entrypoints/cgp_namespace.md deleted file mode 100644 index 7370cfde..00000000 --- a/docs/implementation/entrypoints/cgp_namespace.md +++ /dev/null @@ -1,140 +0,0 @@ -# `cgp_namespace!` — implementation - -`cgp_namespace!` builds a reusable, inheritable wiring table — a *namespace* — by parsing a `delegate_components!`-style body with a namespace header and emitting a lookup trait, an optional marker struct, and one `impl` of that trait per entry. This document covers how that works internally; for the accepted syntax and the complete expansion a user sees, read the reference document [reference/macros/cgp_namespace.md](../../reference/macros/cgp_namespace.md). - -## Entry point - -The macro is driven by the thin `cgp_namespace` function in [cgp-macro-lib/src/cgp_namespace.rs](../../../crates/macros/cgp-macro-lib/src/cgp_namespace.rs). Unlike the attribute macros, it is a function-like macro: it parses the whole body into a single `NamespaceTable`, evaluates it, and renders the result. - -```rust -let namespace_table: NamespaceTable = parse2(body)?; -Ok(namespace_table.eval()?.to_token_stream()) -``` - -There is no separate attribute to parse — the header (`new`, the namespace name, the optional `: parent`, and the generic list) and the entry table are all part of the one `NamespaceTable`, whose `Parse` impl enforces the grammar. A malformed header or entry is rejected there. - -## Pipeline - -The macro is a two-step pipeline: parse into a `NamespaceTable`, then `eval` into an `EvaluatedNamespaceTable` that renders itself with `ToTokens`. The [`namespace` AST stack](../asts/namespace.md) documents those types in full. - -- **parse** reads the header and the entry table. The entry table is the same `DelegateEntries` type that `delegate_components!` parses, so a namespace body accepts the same mappings, array keys, `open`/`namespace`/`for` statements, and `=>` redirects. -- **eval** builds each output item: the lookup trait (only with `new`), the marker struct (only with `new`), one `impl` per evaluated entry, and — when a parent is named — a leading inheritance `impl`. These are collected into the `EvaluatedNamespaceTable`, which emits them in a fixed order through its `ToTokens`. - -## Generated items - -A `new` namespace emits, in order, the marker struct `__{Namespace}Components`, the lookup trait `{Namespace}<__Table__>` carrying a single `type Delegate;`, and one `impl {Namespace}<__Table__> for {Key}` per entry; omitting `new` emits only the per-entry impls, so the trait and struct must already be declared elsewhere. When the header names a parent (`: DefaultNamespace`), an inheritance impl is inserted ahead of the entry impls. The struct-then-trait-then-impls order is what the canonical snapshots pin. - -Each entry lowers to one impl of the lookup trait, keyed on the entry's key type, whose `Delegate` associated type is the entry's target. A `=>` redirect points the delegate at a `RedirectLookup` along the entry's type-level path, while a plain `:` mapping points it straight at a provider. So a redirect and a direct mapping produce the same shape of impl with a different `Delegate`: - -```rust -// new MyNamespace { FooProviderComponent => @MyApp.MyFooComponent, } -impl<__Table__> MyNamespace<__Table__> for FooProviderComponent { - type Delegate = RedirectLookup<__Table__, PathCons>>; -} - -// new DefaultShowComponents { [String, u64]: ShowWithDisplay, } -impl<__Table__> DefaultShowComponents<__Table__> for String { - type Delegate = ShowWithDisplay; -} -``` - -The `@`-path in a redirect desugars along the same rules as elsewhere in CGP: a lowercase segment (`@my_app`) becomes a `Symbol!` in the `PathCons` spine, an uppercase segment (`@MyApp`) stays a type name, and the segments nest right-to-left into `PathCons<…, Nil>`. An array key expands to one impl per component in the array, all sharing the same `Delegate`. - -The **inheritance impl** is what makes a namespace extend a parent. It is built through the same for-entry machinery `delegate_components!` uses, and lowers a parent-namespace clause into a blanket impl that reads each of the parent's entries and re-emits it under the child, so the child inherits every wiring the parent defines: - -```rust -// new ExtendedNamespace: DefaultNamespace { } -impl<__Table__, __Key__, __Value__> ExtendedNamespace<__Table__> for __Key__ -where - __Key__: DefaultNamespace<__ExtendedNamespaceComponents>, - __Key__: DefaultNamespace<__Table__, Delegate = __Value__>, -{ - type Delegate = __Value__; -} -``` - -A `=>` entry in the child body then layers a *path-prefix rewrite* on top of the inheritance: an entry like `@cgp.core.error => @app` emits an impl keyed on the `PathCons` spine of the source prefix followed by a `__Wildcard__` tail, whose delegate redirects to the same wildcard under the rewritten prefix. This is how a child namespace shadows one branch of an inherited path without touching the rest. - -## Behavior and corner cases - -The **`new` keyword** is the switch between defining a namespace and extending an existing one. With `new`, the trait and marker struct are emitted; without it, only the entry impls are, and the caller is responsible for having declared the trait. This is why an inheriting namespace and a plain grouping both start with `new` in practice. - -The **`__Table__` parameter** threads the caller's own table through every impl, so a namespace is not tied to one context: the lookup trait is generic over `__Table__`, each entry impl carries it, and a `RedirectLookup` delegate re-routes lookups back through it. That is what lets many contexts share one namespace, each supplying its own `__Table__`. - -The **reserved identifiers** appear literally in the output. The lookup trait's table parameter is `__Table__`; the inheritance impl introduces `__Key__` and `__Value__`; and a path-prefix rewrite introduces `__Wildcard__` for the inherited tail. The marker struct is always `__{Namespace}Components`, formed by prefixing the namespace identifier with `__` and suffixing `Components`. - -The body **reuses the `delegate_components!` grammar wholesale** — array keys, the leading `open { … }` header, `namespace`/`for` statements, and both `:` and `=>` mappings all parse, because the entries are a `DelegateEntries`. Anything `delegate_components!` accepts in a table, a namespace body accepts too. - -**Attributes are rejected**, matching `delegate_components!`. The entry grammar parses `#[..]` attributes on keys (a `DelegateEntries` reuses the same key parsers), but no attribute is supported, so the entry point calls `entries.validate_attributes()` after parsing to fail with a spanned "unsupported attribute" error rather than silently dropping one. The check recurses through the statement forms, so an attribute on a key inside a `for` loop is rejected too. - -A **`for` loop's optional `where` clause** is applied. `ForDelegateStatement` parses a `WhereClause?` after the `in` table, and `eval_for_entries` merges its predicates into every generated impl alongside the reconstructed `__Key__: Namespace<…, Delegate = __Value__>` bound, so a bound written on the loop constrains which keys it wires. This applies wherever a `for` loop appears — a namespace body or a context's `delegate_components!` table. - -## Error spans - -Each generated namespace impl is re-spanned onto the entry that produced it, so a coherence conflict (`E0119`) between two entries mapping the same key points at the offending entry rather than at the whole `cgp_namespace!` block. The impls are built with `parse_internal!`, whose tokens carry the macro's `call_site` span; `build_namespace_impl` therefore re-spans each finished impl's boundary tokens (its `impl` keyword and `{ … }` body) onto the entry's `span` — leaving the interior, including the mapped value and any per-entry generic, at its own spans — the same `respan_impl` technique the `DelegateComponent`/`IsProviderFor` impls use — see [delegate_components.md](delegate_components.md#error-spans). This matters most for a `=>` prefix-rewrite entry, whose key type is a synthesized `PathCons<..>` nest at `call_site`; the entry instead sources its span from the path segments the user wrote, so the caret lands on the duplicated path leaf. The `#[prefix(...)]` attribute impl is built separately (`PrefixAttribute::to_namespace_impl`) and keyed on the component name, which already carries the trait's own span. - -## Failure modes - -Some namespace input is accepted by the macro but fails to compile downstream, deferred to the compiler because it lacks the whole-program view the check needs. Each is intended behavior, not a bug, and its full anatomy lives in the [error catalog](../../errors/README.md); the [Error spans](#error-spans) section below covers how each caret is aimed at the offending entry. - -Three distinct namespace mistakes all surface as `E0119`, and the catalog splits them by usage rather than by the shared error code. A **duplicate key** — the same key mapped twice in one `cgp_namespace!` block — is a plain [conflicting wiring](../../errors/wiring/conflicting-wiring.md). **Overriding a key a namespace already claims** — a context wiring a path the joined namespace registers, or a child namespace redefining a key it inherits — is the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md): a `namespace N;` header emits a blanket `impl DelegateComponent for Ctx where Key: N` covering every path `N` resolves, and the inheritance impl covers every key the parent binds, so a specific entry for a claimed key overlaps one of them; to override, target a path the namespace routes *to* but does not terminate. **Emitting two blanket forwardings** — joining two namespaces, or a bare-key `for in Table { Key: Value }` loop alongside a `namespace` join — is the [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md): each covers every key, so the two overlap, which is why a context joins one namespace and a loop key must sit inside a path. - -**Registering into a namespace the crate does not own, keyed on a type it does not own**, fails the orphan rule (`E0210`) — the [orphan-rule violation](../../errors/wiring/orphan-rule.md) error class. This covers a `#[default_impl]` on a *prefixed* component (`impl Namespace<_> for PathCons<..>`, offending parameter `__Components__`), a `#[default_impl]` keyed on a foreign *unprefixed* component marker (`impl Namespace<_> for GreeterComponent`), and a `cgp_namespace!` block *without* `new` re-opening a foreign namespace (`impl ForeignNamespace<_> for Key`, offending parameter `__Table__`, caret on the whole block) — each an impl of a foreign trait for a foreign type. The orphan-safe alternatives are owning either end: register from the namespace's crate, key on a local component, use a namespace body entry, or inherit the namespace into a new local one. - -**Joining a namespace that routes a component to a path no entry binds** produces an `E0277` when the wiring is checked — the [unregistered namespace path](../../errors/checks/unregistered-namespace-path.md) error class. A `#[prefix]` registers the *routing* (the namespace resolves the component to a `RedirectLookup` along the path) but not a provider at the path's leaf; if nothing (a `#[default_impl]`, a body entry, or a direct `@path:` line) ever binds one, the `RedirectLookup` finds no `DelegateComponent` entry and the lookup fails. This is a *lookup* failure, distinct from an unsatisfied *dependency*. - -**A circular namespace parent chain** (`new A: B` together with `new B: A`, or a self-inheriting `new A: A`) overflows the trait solver with `E0275` — the [namespace inheritance cycle](../../errors/wiring/namespace-inheritance-cycle.md) error class. The inheritance blanket impl's `where` clause requires the parent's lookup trait, so a looping parent chain yields a `where` clause that never discharges; unlike the lazy `UseContext` [wiring cycle](../../errors/wiring/wiring-cycle.md), the compiler catches it *eagerly* while checking each generated inheritance impl, so both `cgp_namespace!` definitions carry the overflow with no use site required. - -## Snapshots - -Every `snapshot_cgp_namespace!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint: - -- [namespaces/namespace_basic.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_basic.rs) — a `new` namespace with one `=>` redirect to a bare component, showing the struct, trait, and single `RedirectLookup` impl over a two-element path. -- [namespaces/namespace_symbol_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_symbol_path.rs) — the same shape with a lowercase leading segment (`@my_app.…`), so the path's head is a `Symbol!` rather than a type name. -- [namespaces/namespace_type_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_type_path.rs) — an uppercase leading segment (`@MyApp.…`) that stays a type name in the `PathCons` spine. -- [namespaces/namespace_multi.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_multi.rs) — two namespaces defined in one module (`MyNamespace` with a type-path head, `OtherNamespace` with a symbol head), confirming distinct marker structs and traits. -- [namespaces/extended.rs](../../../crates/tests/cgp-tests/tests/namespaces/extended.rs) — a `: DefaultNamespace` parent with a `@cgp.core.error => @app` prefix rewrite, capturing both the inheritance blanket impl and the wildcard-tail path-rewrite impl. -- [namespaces/default_impls.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls.rs) — an array-key direct-mapping namespace (`[String, u64]: ShowWithDisplay`) expanding to one impl per key, and an empty `: DefaultNamespace` extension emitting only the inheritance impl. - -One variant has no dedicated `snapshot_cgp_namespace!` yet: a namespace body carrying a `namespace` or `for` statement (as opposed to plain mappings and `=>` redirects), which is exercised only through the wiring tests below. - -## Tests - -The behavioral tests confirm the generated namespaces wire correctly: - -- [namespaces/namespace_group.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_group.rs) wires a context to a namespace and checks the grouped components resolve. -- [namespaces/extended_namespace_wiring.rs](../../../crates/tests/cgp-tests/tests/namespaces/extended_namespace_wiring.rs) checks that an extended namespace inherits its parent's entries and that the child's prefix rewrite takes effect. -- [namespaces/default_impls_wiring.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs) checks that a `DefaultNamespace`-based namespace supplies defaults a context can override. -- [namespaces/multi_param_namespace.rs](../../../crates/tests/cgp-tests/tests/namespaces/multi_param_namespace.rs) and [namespaces/prefix_default_namespace.rs](../../../crates/tests/cgp-tests/tests/namespaces/prefix_default_namespace.rs) exercise parameterized namespaces and the `#[prefix(...)]` join that attaches a component to one. -- [namespaces/for_where_clause.rs](../../../crates/tests/cgp-tests/tests/namespaces/for_where_clause.rs) pins the `for <..> in .. where ..` loop's `where` clause landing on each generated impl (a `snapshot_delegate_components!`, since `delegate_components!` owns the `for`-loop form). -- [namespaces/default_impl_use_type.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs) registers a provider with a `#[use_type]` abstract-type dependency into a namespace via `#[default_impl]` and resolves it through a context that joins the namespace. Its `snapshot_cgp_impl!` pins that the namespace-registration impl carries no `where` clause — the provider's impl-side bounds must not leak onto the registration impl's `Self` (the path key), or the abstract-type dependency would be demanded of the path rather than the context. See [asts/attributes/default_impl.md](../asts/attributes/default_impl.md) for the mechanism. - -The cross-crate coverage confirming the orphan-safe direction of the default-impl coherence rule now lives in `cargo-cgp` (its auxiliary crates `cgp-test-crate-a`/`-b`, migrated out of this repository): - -- [`ok/cross_crate_wiring.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/ok/cross_crate_wiring.rs) builds `cgp-test-crate-b`, which registers a *local* component into `cgp-test-crate-a`'s foreign `AppNamespace` with `#[default_impl(FarewellComponent in AppNamespace)]` — legal because the crate owns the component key — and resolves it through a local context that joins the namespace. Its failing orphan-rule counterparts are the `usability/wiring/orphan/` fixtures cataloged under [orphan-rule](../../errors/wiring/orphan-rule.md). - -The rejection cases in `cgp-macro-tests` pin the attribute rejection: - -- [parser_rejections/cgp_namespace.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_namespace.rs) asserts the macro rejects an attribute on a `:` mapping key, on a `=>` redirect key, and on a key inside a `for` loop. - -The post-codegen compile-fail cases are now `cargo-cgp` UI fixtures that pin accepted expansions failing to compile — all **acceptable** failures (deferred to the compiler by design, none a defect), documented in the [error catalog](../../errors/README.md); the [Failure modes](#failure-modes) section links each to its class. `cargo-cgp` files each fixture by the *quality of the output* it renders (`acceptable/` when it already leads with the cause, `usability/` when the cause is present but buried), which is why the orphan-rule cases below sit under `usability/` even though none is a defect: - -- [`acceptable/wiring/namespace-paths/namespace_duplicate_path_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/namespace_duplicate_path_key.rs) — two identical `@`-path prefix-rewrite entries conflict (`E0119`); its `.rust.stderr` pins the [error span](#error-spans) landing on the duplicated path leaf rather than the whole block, even though the key lowers to a synthesized `PathCons<..>` type. -- [`acceptable/wiring/namespace-paths/override_registered_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/override_registered_path.rs) — a context joins a namespace that registers a path (via `#[default_impl]`) and also wires that path directly, so the `namespace` blanket impl and the direct entry both implement `DelegateComponent` for the path (`E0119`, with the `downstream crates may implement` note); the context-level shape of the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md) class. -- [`acceptable/wiring/namespace-paths/inherited_override_conflict.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/inherited_override_conflict.rs) — a child namespace that redefines a key its parent binds, so the inheritance blanket impl and the child's specific entry overlap (a *single* `E0119` on `ChildNs<_>` for the component marker, no note); the namespace-level shape of the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md) class. -- [`acceptable/wiring/duplicate-keys/for_loop_bare_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/for_loop_bare_key.rs) — a bare-key `for` loop alongside a `namespace` join, whose two blanket `DelegateComponent`/`IsProviderFor` impls overlap (`E0119`, fully-generic carets, no downstream note); the [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md) class. -- [`acceptable/wiring/namespace-paths/two_namespaces_joined.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/two_namespaces_joined.rs) — two `namespace` joins on one context, each emitting a blanket forwarding impl over every key, which overlap (`E0119`, fully-generic carets); the same [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md) class. -- [`usability/wiring/orphan/default_impl_foreign_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/default_impl_foreign_prefix_path.rs) — a downstream crate registers a default for an upstream *prefixed* component into the upstream namespace, whose `impl Namespace<_> for PathCons<..>` is wholly foreign and violates the orphan rule (`E0210` on `__Components__`); the [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. A cross-crate case: an `//@aux-build` upstream crate supplies the component and namespace. -- [`usability/wiring/orphan/default_impl_foreign_component.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/default_impl_foreign_component.rs) — the same orphan keyed on a foreign *unprefixed* component marker rather than a path (`E0210` on `__Components__`); shows the restriction is not specific to prefixes. Same [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. -- [`usability/wiring/orphan/reopen_foreign_namespace.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/reopen_foreign_namespace.rs) — a `cgp_namespace!` block without `new` re-opening a foreign namespace, whose entry impl is foreign (`E0210` on `__Table__`, caret on the whole block); same [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. -- [`acceptable/resolution/unregistered_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/resolution/unregistered_prefix_path.rs) — a `#[prefix]`-ed component joined through its namespace with no provider bound at its path, so a `check_components!` reports the redirect target's `DefaultNamespace`/`DelegateComponent` lookup as unsatisfied (`E0277`); the [unregistered namespace path](../../errors/checks/unregistered-namespace-path.md) class. - -The **namespace inheritance cycle** is the one class with no `cargo-cgp` fixture: under cargo-cgp's next-generation trait solver the two mutually-inheriting namespaces compile clean, with no `E0275` left to snapshot — a *missing*-error divergence (the reverse of the usual next-solver caveat) that makes this the single un-migrated class, recorded as such in [cargo-cgp's tests/README.md](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md). The eager `E0275` overflow at both `cgp_namespace!` definitions remains a plain-`rustc` behavior, described in the [namespace inheritance cycle](../../errors/wiring/namespace-inheritance-cycle.md) class. - -## Source - -- Entry point: `cgp_namespace` in [cgp-macro-lib/src/cgp_namespace.rs](../../../crates/macros/cgp-macro-lib/src/cgp_namespace.rs). -- Pipeline and its AST types: [cgp-macro-core/src/types/namespace/](../../../crates/macros/cgp-macro-core/src/types/namespace/), documented in [asts/namespace.md](../asts/namespace.md). -- The entry table reuses the `DelegateEntries` grammar from [cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/); the inheritance impl comes from `inherit.rs`. -- The `RedirectLookup` marker and every generated fragment are built with [parse_internal!](../macros/parse_internal.md). -- The `#[prefix(...)]` attribute that attaches a component to a namespace is handled as part of `#[cgp_component]`; see [entrypoints/cgp_component.md](cgp_component.md). diff --git a/docs/implementation/entrypoints/cgp_new_provider.md b/docs/implementation/entrypoints/cgp_new_provider.md deleted file mode 100644 index 3372df90..00000000 --- a/docs/implementation/entrypoints/cgp_new_provider.md +++ /dev/null @@ -1,44 +0,0 @@ -# `#[cgp_new_provider]` — implementation - -`#[cgp_new_provider]` is [`#[cgp_provider]`](cgp_provider.md) with the `new` keyword forced on: it runs the identical lowering but also declares the provider struct, so a provider impl and its `Self` type are defined in one place. This document covers only what distinguishes it from `#[cgp_provider]`; for the accepted syntax and the full expansion, read the reference document [reference/macros/cgp_new_provider.md](../../reference/macros/cgp_new_provider.md). - -## Entry point - -The macro is driven by the `cgp_new_provider` function in [cgp-macro-lib/src/cgp_new_provider.rs](../../../crates/macros/cgp-macro-lib/src/cgp_new_provider.rs). It parses the same `ProviderArgs` and the same `syn::ItemImpl` as `#[cgp_provider]`, sets `args.new` to enabled, and then runs the identical `ItemCgpProvider::lower`: - -```rust -let mut args: ProviderArgs = parse2(attr)?; -args.new = Some(Keyword::default()); -let lowered = ItemCgpProvider { args, item_impl }.lower()?; -``` - -Because everything downstream is shared, the argument grammar, the error paths, and the corner cases are exactly those of `#[cgp_provider]` — the attribute takes only the optional component-type override (the `new` keyword is implied by the macro name, not written in the argument). - -## Pipeline - -The pipeline is `#[cgp_provider]`'s single `ItemCgpProvider::lower` stage; the [`cgp_provider` AST stack](../asts/cgp_provider.md) documents it in full. The only behavioral difference is that `new` is always set, so `to_provider_struct` always emits a struct rather than returning `None`. - -## Generated items - -The macro emits the provider impl (verbatim), the derived `IsProviderFor` impl, and the provider struct — the same three items as `#[cgp_impl(new …)]` and the same first two as `#[cgp_provider]`. See [`#[cgp_provider]`'s Generated items](cgp_provider.md#generated-items) for how the `IsProviderFor` arguments are assembled, and its [Behavior and corner cases](cgp_provider.md#behavior-and-corner-cases) for the struct shape (unit struct for a plain name, `PhantomData` field for a generic provider). - -## Known issues - -None beyond those inherited from [`#[cgp_provider]`](cgp_provider.md#known-issues): a const argument in the provider trait's arguments is rejected with a spanned error. - -## Snapshots - -`#[cgp_new_provider]` has no `snapshot_cgp_new_provider!` invocation across the suite, so its expansion is not pinned directly. The equivalent output is covered indirectly by the `#[cgp_impl(new …)]` snapshots — which desugar to `#[cgp_new_provider]` — indexed in [`#[cgp_impl]`'s Snapshots section](cgp_impl.md#snapshots), and by the const-generic and lifetime `snapshot_cgp_provider!` cases indexed in [`#[cgp_provider]`'s Tests section](cgp_provider.md#tests). A dedicated `#[cgp_new_provider]` snapshot — showing the struct declaration emitted directly rather than via the `#[cgp_impl]` sugar — is a missing variant. - -## Tests - -`#[cgp_new_provider]` is exercised directly in real wiring: - -- [dispatching/compose.rs](../../../crates/tests/cgp-tests/tests/dispatching/compose.rs) defines composed providers with `#[cgp_new_provider]` and wires them into a context. -- [async_and_send/spawn.rs](../../../crates/tests/cgp-tests/tests/async_and_send/spawn.rs) defines a generic `SpawnAndRun` provider with `#[cgp_new_provider]`, exercising the `PhantomData`-field struct shape. - -## Source - -- Entry point: `cgp_new_provider` in [cgp-macro-lib/src/cgp_new_provider.rs](../../../crates/macros/cgp-macro-lib/src/cgp_new_provider.rs). -- Generation logic — including the struct declaration built by `to_provider_struct` when `new` is set — shared with `#[cgp_provider]`: [cgp-macro-core/src/types/cgp_provider/](../../../crates/macros/cgp-macro-core/src/types/cgp_provider/), documented in [asts/cgp_provider.md](../asts/cgp_provider.md). -- The struct-declaring sugar [`#[cgp_impl(new …)]`](cgp_impl.md) desugars to this macro. diff --git a/docs/implementation/entrypoints/cgp_producer.md b/docs/implementation/entrypoints/cgp_producer.md deleted file mode 100644 index cd794bda..00000000 --- a/docs/implementation/entrypoints/cgp_producer.md +++ /dev/null @@ -1,55 +0,0 @@ -# `#[cgp_producer]` — implementation - -`#[cgp_producer]` turns a no-argument function into a [`Producer`](../../reference/components/producer.md) provider by emitting a `#[cgp_new_provider]` impl that calls the function and a `delegate_components!` block that promotes the whole handler family from it. This document covers how the macro is built; for the accepted syntax and the full expansion, read the reference document [reference/macros/cgp_producer.md](../../reference/macros/cgp_producer.md). - -## Entry point - -The macro is the `cgp_producer` function in [cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs). Like [`#[cgp_computer]`](cgp_computer.md), it is a self-contained procedural function rather than a driver over a `cgp-macro-core` AST stack: it parses the body into a `syn::ItemFn`, resolves the provider name (the attribute tokens if present, otherwise the function name in PascalCase via `to_camel_case_str`), validates the signature, and assembles the output with `quote!`. - -## Pipeline - -There is no staged AST pipeline and, unlike `#[cgp_computer]`, no branching — a producer has exactly one shape. The function validates the signature against the producer's constraints and then emits a fixed set of three items. The three constraints are each checked before any code is generated: - -- **no parameters** — a producer takes no input and no `self` receiver. -- **not `async`** — the `Producer` trait is synchronous. -- **no generic parameters** — the producer impl introduces only the reserved context and code parameters. - -Each violation returns a spanned `syn::Error` pointing at the offending part of the signature. - -## Generated items - -The macro emits three items: the original function unchanged, a `#[cgp_new_provider]` impl of the [`Producer`](../../reference/components/producer.md) trait, and a `delegate_components!` block. The impl introduces the reserved `__Context__` and `__Code__` parameters, ignores both in its `produce` body, and simply calls the function: - -```rust -// #[cgp_producer] fn magic_number() -> u64 { 42 } -#[cgp_new_provider] -impl<__Context__, __Code__> Producer<__Context__, __Code__> for MagicNumber { - type Output = u64; - - fn produce(_context: &__Context__, _code: PhantomData<__Code__>) -> Self::Output { - magic_number() - } -} -``` - -The `delegate_components!` block then routes all eight handler components — including `ComputerComponent`, which `#[cgp_computer]` never delegates because the computer *is* its own base — to the single `PromoteProducer` bundle. Because a producer ignores its input, that bundle lets every handler shape yield the produced value regardless of any input it is handed. There is no `Result` analysis: the `Output` associated type is the return type verbatim, whether or not it is a `Result`. - -## Behavior and corner cases - -An **omitted return type** defaults to `()`, so a producer written with no return arrow yields `Output = ()`. - -The three signature checks are the macro's only corner cases, and all reject rather than reinterpret: a parameter, an `async` keyword, or a generic parameter each aborts expansion with a spanned error rather than being silently accepted. This is stricter than `#[cgp_computer]`, which accepts parameters, `async`, and generics. - -## Tests - -The behavioral test exercises the generated provider across the handler family: - -- [handlers/producer_macro.rs](../../../crates/tests/cgp-tests/tests/handlers/producer_macro.rs) — an input-free function called as `produce`, `compute`, `try_compute`, `compute_async`, and `handle` plus their `…Ref` variants, all yielding the same value. - -There is no dedicated `snapshot_cgp_producer!` macro; the macro's expansion is not pinned by a snapshot and is exercised only behaviorally. - -## Source - -- Entry point: `cgp_producer` in [cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs), forwarded from the proc-macro shim in [cgp-extra-macro/src/lib.rs](../../../crates/macros/cgp-extra-macro/src/lib.rs). -- The emitted items lean on [`#[cgp_new_provider]`](cgp_new_provider.md) for the base impl and [`delegate_components!`](delegate_components.md) for the wiring. -- The input-carrying sibling macro is [`#[cgp_computer]`](cgp_computer.md). diff --git a/docs/implementation/entrypoints/cgp_provider.md b/docs/implementation/entrypoints/cgp_provider.md deleted file mode 100644 index ed453529..00000000 --- a/docs/implementation/entrypoints/cgp_provider.md +++ /dev/null @@ -1,80 +0,0 @@ -# `#[cgp_provider]` — implementation - -`#[cgp_provider]` takes a provider-trait impl written directly on a provider struct, passes it through unchanged, and derives the matching [`IsProviderFor`](../../reference/traits/is_provider_for.md) impl from the same `where` clause so the provider's dependencies can never drift out of sync. This document covers how that derivation works internally; for the accepted syntax and the full expansion, read the reference document [reference/macros/cgp_provider.md](../../reference/macros/cgp_provider.md). - -## Entry point - -The macro is driven by the `cgp_provider` function in [cgp-macro-lib/src/cgp_provider.rs](../../../crates/macros/cgp-macro-lib/src/cgp_provider.rs). It parses the item into a `syn::ItemImpl` and the attribute into a `ProviderArgs`, then lowers the impl and emits the result: - -```rust -let item = ItemCgpProvider { args, item_impl }; -let lowered = item.lower()?; -``` - -`ProviderArgs`'s parser reads only an optional component-type override, so `#[cgp_provider]`'s grammar is exactly `ComponentType?`. The `new` flag on `ProviderArgs` is never parsed from the attribute; whether the provider struct is declared is decided by which macro is invoked, not by a keyword. A stray `#[cgp_provider(new Name)]` is therefore not a struct declaration — the component-type parser consumes `new` as a (nonsensical) component type and then rejects the trailing `Name` as an unexpected token. - -The sibling [`#[cgp_new_provider]`](cgp_new_provider.md) shares this whole stack: its `cgp_new_provider` entry parses the same `ProviderArgs`, forces `args.new = Some(...)` *after* parsing, and then runs the identical `ItemCgpProvider::lower`. The two macros differ only in whether the provider struct is declared. A malformed attribute is rejected while parsing `ProviderArgs`, and a non-`impl` item fails at `syn::parse2::`. - -## Pipeline - -The macro has a single lowering stage, `ItemCgpProvider::lower`, which produces a `LoweredCgpProvider` bundling three items; the [`cgp_provider` AST stack](../asts/cgp_provider.md) documents those types in full. Within that one stage the work splits three ways: - -- deriving the **component type** — defaulting to the provider trait's name with a `Component` suffix, or using the attribute's explicit override; -- deriving the **`IsProviderFor` impl** — cloning the provider impl, stripping its body and associated types, and swapping the trait for `IsProviderFor`; -- deriving the **provider struct** — emitted only when `new` is set. - -## Generated items - -The macro emits the provider impl (verbatim), the derived `IsProviderFor` impl, and — for `#[cgp_new_provider]` or `#[cgp_impl(new …)]` — the provider struct, in that order. The one piece of real synthesis is the `IsProviderFor` impl. - -Its trait arguments are split out of the provider trait's own arguments: the first is the component type, the second is the provider trait's **leading** type argument (the context), and the third is a tuple of every **remaining** type parameter. A lifetime among the remaining arguments is lifted into `Life<'a>` in that tuple. So a multi-parameter provider trait yields a grouped `Params` tuple: - -```rust -// provider impl -impl ComputerRef for FirstNameToString -where Context: HasField { /* … */ } - -// derived alongside it -impl - IsProviderFor for FirstNameToString -where Context: HasField {} -``` - -The derived impl keeps the original generic parameters and `where` clause verbatim, so it holds under exactly the conditions the provider impl holds. Its body and associated types are cleared, and its `defaultness`/`unsafety` are dropped. - -## Behavior and corner cases - -The **component type defaults** from the provider trait's identifier: `component_type()` reads the trait path, takes its identifier, and appends `Component`, so implementing `AreaCalculator` targets `AreaCalculatorComponent`. The optional attribute argument overrides this with an explicit type — the only thing the attribute changes. - -The **context is the first type argument** of the provider trait, and it must be present. `ProviderImplArgs::from_generic_args` walks the trait's arguments in order, takes the first *type* argument as the context, and collects the rest into the `Params` tuple; lifetimes always go into the tuple (lifted to `Life<'a>`) regardless of position. A provider trait path with no type argument at all is rejected with a spanned error. - -The **`new` keyword controls the struct**, whose shape is read from the impl's `Self` type by `to_provider_struct`. A plain provider name yields a unit `pub struct Name;`. A generic provider yields a struct with a `PhantomData` field binding its parameters — a lifetime parameter is bound via `Life<'a>` inside the `PhantomData` tuple: - -```rust -// #[cgp_new_provider] impl Runner for SpawnAndRun -pub struct SpawnAndRun(pub ::core::marker::PhantomData); -``` - -The **provider-name rewrite in the generics** keeps the `IsProviderFor` bounds honest: `replace_provider_in_generics` rewrites any `Provider: SomeTrait` bound in the `where` clause into an `IsProviderFor` bound, so a higher-order provider's inner-provider dependency surfaces in error messages as an `IsProviderFor` obligation rather than a raw provider-trait bound. - -## Known issues - -A **const argument in the provider trait's arguments** is rejected with a spanned error rather than supported: `ProviderImplArgs::from_generic_args` returns "const arguments are not supported in provider impl trait arguments" if a `const` appears among the trait's type arguments. A const generic on the *provider struct* is fine — it flows through untouched — so this limits only const parameters that sit in the provider trait's own argument list. - -## Tests - -`#[cgp_provider]` is exercised both by expansion snapshots (see [`#[cgp_component]`](cgp_component.md#snapshots) for the `snapshot_cgp_provider!` index, which lives with the component feature) and by direct behavioral use: - -- [generic_components/component_const.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_const.rs) snapshots a const-generic provider `UseConstant`, with the const on the struct flowing through to the `IsProviderFor` impl unchanged. -- [generic_components/component_generic_const.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_generic_const.rs) snapshots the same const-generic provider carrying an impl-side dependency that ties the const's type to the context's abstract `Unit`. -- [generic_components/component_lifetime.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs) snapshots a lifetime-carrying `UseField` provider, showing the lifetime lifted into `Life<'a>` in the `Params` tuple. -- [dispatching/compose.rs](../../../crates/tests/cgp-tests/tests/dispatching/compose.rs) and [async_and_send/spawn.rs](../../../crates/tests/cgp-tests/tests/async_and_send/spawn.rs) exercise `#[cgp_provider]` and `#[cgp_new_provider]` directly in real wiring. -- [parser_rejections/cgp_provider.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_provider.rs) checks that an inherent `impl` (no trait), a provider trait with no context type argument, a const argument in the trait's argument list, the `new Name` struct-declaration form (which belongs to `#[cgp_new_provider]`), and a non-`impl` item are each rejected. - -## Source - -- Entry points: `cgp_provider` in [cgp-macro-lib/src/cgp_provider.rs](../../../crates/macros/cgp-macro-lib/src/cgp_provider.rs) and `cgp_new_provider` in [cgp-macro-lib/src/cgp_new_provider.rs](../../../crates/macros/cgp-macro-lib/src/cgp_new_provider.rs). -- Shared lowering and its AST types: [cgp-macro-core/src/types/cgp_provider/](../../../crates/macros/cgp-macro-core/src/types/cgp_provider/), documented in [asts/cgp_provider.md](../asts/cgp_provider.md). -- `IsProviderFor` derivation: [cgp-macro-core/src/types/provider_impl.rs](../../../crates/macros/cgp-macro-core/src/types/provider_impl.rs). -- Provider-name-to-`IsProviderFor` rewrite: the [`replace_provider` visitor](../../../crates/macros/cgp-macro-core/src/visitors/replace_provider.rs). -- This macro is the handoff target of [`#[cgp_impl]`](cgp_impl.md). diff --git a/docs/implementation/entrypoints/cgp_type.md b/docs/implementation/entrypoints/cgp_type.md deleted file mode 100644 index 79fa092a..00000000 --- a/docs/implementation/entrypoints/cgp_type.md +++ /dev/null @@ -1,90 +0,0 @@ -# `#[cgp_type]` — implementation - -`#[cgp_type]` builds an abstract-type component by running the `#[cgp_component]` pipeline over a trait carrying one associated type, then appending the two provider impls (`UseType` and `WithProvider`) that let a context choose the concrete type through wiring. This document covers how that works internally; for the accepted syntax and the full expansion, read the reference document [reference/macros/cgp_type.md](../../reference/macros/cgp_type.md). - -## Entry point - -The macro is driven by the `cgp_type` function in [cgp-macro-lib/src/cgp_type.rs](../../../crates/macros/cgp-macro-lib/src/cgp_type.rs). It follows the canonical entry-point shape but with one preparatory step unique to this macro: after parsing the attribute into `CgpComponentRawArgs` and the item into a `syn::ItemTrait`, it extracts the trait's single associated type and, when the user gave no provider name, defaults `provider_ident` to `{Type}TypeProvider` — keyed off the *associated type's* identifier, not the trait's. It then feeds the args and trait into the shared `#[cgp_component]` pipeline and wraps the result in an `ItemCgpType` for the extra codegen. - -```rust -if raw_args.provider_ident.is_none() { - raw_args.provider_ident = Some(Ident::new( - &format!("{}TypeProvider", item_type.ident), - item_type.ident.span(), - )); -} - -let evaluated = item_cgp_component.preprocess()?.eval()?; -let item_cgp_type = ItemCgpType { item_component: evaluated }; -let items = item_cgp_type.to_items()?; -``` - -Three failures surface here: a malformed attribute is rejected while parsing `CgpComponentRawArgs`; a non-trait item fails at `syn::parse2::`; and a trait whose body is not exactly one plain (non-generic, `where`-free) associated type is rejected by `extract_item_type_from_trait`. - -## Pipeline - -`#[cgp_type]` reuses the `#[cgp_component]` pipeline for the component itself and adds a final rendering step of its own. The two shared stages, `preprocess` and `eval`, are exactly the ones documented for the [`cgp_component` entrypoint](cgp_component.md) and are owned by the [`cgp_component` AST stack](../asts/cgp_component.md); this macro does not re-implement them. - -- **preprocess → eval** run the standard `#[cgp_component]` derivation over the associated-type trait, producing an `EvaluatedCgpComponent` — the consumer trait, provider trait, both blanket impls, and the component marker. -- **to_items** is where `#[cgp_type]` diverges: `ItemCgpType::to_items` first calls the evaluated component's own `to_items` (emitting the five core items plus the `UseContext` and `RedirectLookup` provider impls), then appends the `UseType` and `WithProvider` provider impls. The [`cgp_type` AST stack](../asts/cgp_type.md) documents `ItemCgpType`. - -## Generated items - -The macro emits the entire `#[cgp_component]` output for the trait — described in the [`cgp_component` entrypoint document](cgp_component.md), except that every blanket impl forwards an *associated type* rather than a method body — followed by two abstract-type provider impls. Each of the two extra impls is paired with a matching `IsProviderFor` impl carrying the same bounds, produced through the shared [`ItemProviderImpl`/`ItemProviderImpls`](../asts/cgp_type.md) machinery. - -The first extra impl is the `UseType` blanket impl, the heart of the macro. It implements the provider trait for `UseType` by setting the abstract associated type to the free generic parameter, so wiring a component to `UseType` supplies `f64` as the type with no bespoke provider: - -```rust -impl ScalarTypeProvider<__Context__> for UseType { - type Scalar = Scalar; -} -``` - -The second is a `WithProvider` bridge that adapts the built-in `TypeProvider` machinery into this component, so a `#[cgp_type]` component can be backed by any generic `TypeProvider`: - -```rust -impl<__Provider__, Scalar, __Context__> ScalarTypeProvider<__Context__> - for WithProvider<__Provider__> -where - __Provider__: TypeProvider<__Context__, ScalarTypeProviderComponent, Type = Scalar>, -{ - type Scalar = Scalar; -} -``` - -## Behavior and corner cases - -A **bound on the associated type** is threaded not only into the provider trait (that comes for free from the shared component pipeline) but also onto both extra impls. `to_item_provider_impls` reads the bound with [`get_bounds_and_replace_self_assoc_type`](../../../crates/macros/cgp-macro-core/src/visitors/self_assoc_type.rs), which rewrites any `Self::Scalar` inside the bound to the free `Scalar` parameter, then adds it as a `where Scalar: ` predicate on both the `UseType` and `WithProvider` impls. A self-referential bound such as `type Scalar: Mul + Clone` therefore emits `where Scalar: Mul + Clone`. - -**Generic parameters** on the trait are handled entirely by the shared component pipeline and then reused: `to_item_provider_impls` clones the provider trait's generics and inserts the associated-type name (and, for `WithProvider`, `__Provider__`) as leading impl parameters, so a `?Sized` or otherwise-bounded parameter carries through onto the extra impls unchanged. - -The **provider-name default** is the one behavior `#[cgp_type]` adds ahead of the pipeline: an omitted provider name becomes `{Type}TypeProvider` (from the associated type), where a bare `#[cgp_component]` would instead reject a missing name. A supplied name — `#[cgp_type(ProvideFooType)]` — overrides this exactly as it does for `#[cgp_component]`. - -## Snapshots - -Every `snapshot_cgp_type!` invocation across the suite is indexed here; the canonical variants live in the `abstract_types` target, with one `UseDelegate`-focused variant owned by the dispatch target: - -- [abstract_types/cgp_type_macro.rs](../../../crates/tests/cgp-tests/tests/abstract_types/cgp_type_macro.rs) — the canonical plain expansion (one associated type, default `ScalarTypeProvider` name), showing the full component output plus the `UseType` and `WithProvider` impls. -- [abstract_types/cgp_type_bounded.rs](../../../crates/tests/cgp-tests/tests/abstract_types/cgp_type_bounded.rs) — the associated type bounded by another abstract-type component (`type Types: HasScalarType`), with the bound propagating onto the `UseType` and `WithProvider` impls. -- [abstract_types/cgp_type_self_referential.rs](../../../crates/tests/cgp-tests/tests/abstract_types/cgp_type_self_referential.rs) — a self-referential bound (`type Scalar: Mul + Clone`), where `Self::Scalar` is rewritten to the free parameter in the extra impls' `where` clause. -- [abstract_types/cgp_type_unsized.rs](../../../crates/tests/cgp-tests/tests/abstract_types/cgp_type_unsized.rs) — a `#[cgp_type(ProvideFooType)]` overriding the default name together with a `?Sized` generic parameter threaded through every generated item. -- [dispatching/use_delegate_getter.rs](../../../crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs) — a multi-parameter `#[cgp_type]` with `#[derive_delegate]` attributes, pinning the `UseDelegate` dispatch-provider impls; owned by the dispatch concept rather than repeated in the abstract-types target. - -No snapshot yet captures the keyed argument form (`#[cgp_type { provider: …, context: … }]`) distinct from the bare-name override. - -## Tests - -The behavioral tests confirm the generated wiring and the `UseType` route work: - -- [abstract_types/cgp_type_bounded.rs](../../../crates/tests/cgp-tests/tests/abstract_types/cgp_type_bounded.rs) wires a context whose abstract type is itself an abstract-type component and checks the bound is enforced. -- [abstract_types/cgp_type_self_referential.rs](../../../crates/tests/cgp-tests/tests/abstract_types/cgp_type_self_referential.rs) wires a self-referentially bounded type through `delegate_components!` and passes its check. -- [abstract_types/cgp_type_unsized.rs](../../../crates/tests/cgp-tests/tests/abstract_types/cgp_type_unsized.rs) exercises a `?Sized` abstract type together with a dependent getter. - -## Source - -- Entry point: `cgp_type` in [cgp-macro-lib/src/cgp_type.rs](../../../crates/macros/cgp-macro-lib/src/cgp_type.rs). -- Extra codegen: [cgp-macro-core/src/types/cgp_type/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_type/item.rs), documented in [asts/cgp_type.md](../asts/cgp_type.md). -- Shared component pipeline it wraps: [cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/), documented in [entrypoints/cgp_component.md](cgp_component.md). -- Associated-type-bound rewriting: [`get_bounds_and_replace_self_assoc_type`](../../../crates/macros/cgp-macro-core/src/visitors/self_assoc_type.rs). -- Paired provider/`IsProviderFor` impls: `ItemProviderImpl` in [cgp-macro-core/src/types/provider_impl.rs](../../../crates/macros/cgp-macro-core/src/types/provider_impl.rs). -- Fragment construction: [parse_internal!](../macros/parse_internal.md). diff --git a/docs/implementation/entrypoints/check_components.md b/docs/implementation/entrypoints/check_components.md deleted file mode 100644 index fbc618cf..00000000 --- a/docs/implementation/entrypoints/check_components.md +++ /dev/null @@ -1,87 +0,0 @@ -# `check_components!` — implementation - -`check_components!` turns each entry of a check table into a compile-time assertion that a context can use a component, by generating a check trait whose supertrait is the assertion and one empty impl per checked entry. This document covers how that works internally; for the accepted syntax and the complete expansion a user sees, read the reference document [reference/macros/check_components.md](../../reference/macros/check_components.md). - -## Entry point - -The macro is driven by the thin `check_components` function in [cgp-macro-lib/src/check_components.rs](../../../crates/macros/cgp-macro-lib/src/check_components.rs). It parses the body into a [`CheckComponentsTables`](../asts/check_components.md) — one `CheckComponentsTable` per context block — renders each to items, and emits them: - -```rust -let tables: CheckComponentsTables = parse2(body)?; -let items = tables.to_items()?; -Ok(quote! { #( #items )* }) -``` - -All real logic lives in `cgp-macro-core`. A malformed table fails while parsing. Attribute validation also happens during parsing: an unknown table-level attribute (anything other than `#[check_trait]` or `#[check_providers]`) fails with a spanned error, as do a repeated `#[check_trait]`, a repeated `#[check_providers]`, and an empty `#[check_providers()]` (which would otherwise emit a check trait with no impls that verifies nothing). - -## Pipeline - -The macro parses into the [`check_components` AST stack](../asts/check_components.md) and then calls `to_items` on each table, which internally runs a single `eval` per table. Parsing splits each table into its attributes, an optional leading generic list, the context type, an optional `where` clause, and the brace-delimited check entries. `eval` builds the check trait once and then, for each evaluated entry, emits one impl of that trait; there is no multi-stage lowering beyond this. - -## Generated items - -Each table emits one check trait followed by one empty impl per checked entry. The trait is an alias whose sole supertrait is the assertion being made, and each impl compiles only if that supertrait holds for the entry. A bare component with no parameters lowers to a unit `__Params__`: - -```rust -// check_components! { Person { GreeterComponent } } -trait __CheckPerson<__Component__, __Params__: ?Sized>: - CanUseComponent<__Component__, __Params__> -{} -impl __CheckPerson for Person {} -``` - -The impl holds only if `Person: CanUseComponent`, which routes through `IsProviderFor` so an unsatisfied transitive bound (a missing `HasField`, say) is what the compiler reports. The generic parameters are literally `__Component__` and `__Params__` in the output, and the check trait name defaults to `__Check{Context}`, derived from the context type's leading identifier. - -A component with parameters places them in the `__Params__` slot — a single parameter directly, multiple as a tuple. The `#[check_providers(...)]` form changes both the supertrait and the implementing type: the trait supertraits `IsProviderFor<__Component__, Context, __Params__>` instead of `CanUseComponent`, and one impl is written for each listed provider rather than for the context, so each provider is asserted independently. - -## Behavior and corner cases - -**Array syntax expands to the cartesian product.** A bracketed key, a bracketed value, or both, expand to one entry per combination before any impl is emitted, so `[A, B]: [P, Q]` yields four impls. The key and value sides are parsed independently (`CheckKey`, `CheckValue`), and each evaluated entry pairs one key with one value. - -**Table-level generics and `where` clauses are merged onto every impl.** A `<'a, I> Context where I: Clone { … }` table threads both onto each generated impl, and a check parameter may itself be generic (`Component: &'a I` or a value carrying its own `` list), whose generics merge with the table's. - -**The error span is moved onto the component.** For the context-checking form the macro overrides the span of the context type in each impl with the span of the checked component (or parameter), so an unsatisfied-constraint error is highlighted on the component the user wrote rather than on the context. The `#[check_providers(...)]` form skips this, since it implements for the providers instead. - -**A component with no value** emits a single unit-params entry; a bracketed value that is empty is treated the same way. - -**The check trait name is derived from the context type's final path segment.** `derive_check_trait_ident` parses the context type through `PathWithTypeArgs` and prepends `__Check` to the last segment's identifier, so a path-qualified context such as `some_mod::Context` yields `__CheckContext` and is accepted rather than rejected — matching `delegate_components!`, which uses the context type verbatim. - -## Failure modes - -A few `check_components!` inputs are accepted by the macro but rejected — or silently do nothing — downstream, each intended behavior rather than a bug. Unlike the empty `#[check_providers()]` the parser rejects outright, these are left to the compiler or to the user because catching them would require second-guessing a legitimate, if degenerate, request. - -An **unsatisfied dependency** is the central intended failure and the reason the macro exists: a checked component whose provider has an impl-side dependency the context cannot meet fails at the check site rather than lazily at the eventual call site. Its error anatomy — the `E0277` that names the missing bound through `IsProviderFor`, and the contrast with the hidden call-site error the unchecked wiring produces — is documented as the [check-trait failure](../../errors/checks/check-trait-failure.md) error class in the catalog. What *this* document pins is the **span**: the unsatisfied-bound caret lands on the component inside the `check_components!` block, not on the shared context type, because the check impl re-spans that one context token onto each listed component in turn with `override_span`. The `cargo-cgp` UI fixture [`acceptable/fields/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/missing_dependency.rs) is the regression test for that re-span; the [`delegate_components!` counterpart](delegate_components.md) leaves the same wiring unchecked to show the contrasting lazy error. - -An **empty check table** (`Context { }`, or every entry checking nothing) emits the check trait with no impls, so it compiles and verifies nothing. This is not rejected because a table trimmed down to nothing during editing is indistinguishable from a deliberately empty one, and an empty table causes no harm — it simply asserts nothing. - -A **duplicate check entry** — the same component and parameters listed twice, directly (`Context { FooComponent, FooComponent }`) or through array expansion (`[A, A]: P`) — emits two identical check impls and fails with `E0119`, the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. The span override aims the conflict at the repeated component. - -**Two tables for the same context with no `#[check_trait]` override** both derive the same `__Check{Context}` name and emit conflicting trait definitions, failing with `E0428` (the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class). The fix is a `#[check_trait(Name)]` on one table, which is why the override exists. - -## Snapshots - -Every `snapshot_check_components!` invocation across the suite is indexed here, since these snapshots belong to this entrypoint: - -- [checking/check_trait.rs](../../../crates/tests/cgp-tests/tests/checking/check_trait.rs) — the standalone check form: multiple check blocks in one invocation, each renamed with `#[check_trait(...)]`, per-entry parameter lists for generic-parameter components, and an array key checked against a parameter list. -- [checking/check_generic.rs](../../../crates/tests/cgp-tests/tests/checking/check_generic.rs) — a generic context (`<'a, I>` plus `where I: Clone`) whose generics and clause are carried onto each impl, a check parameter that uses a generic (`Component: &'a I`), and a component that is itself generic (`BarGetterAtComponent`). -- [checking/check_providers.rs](../../../crates/tests/cgp-tests/tests/checking/check_providers.rs) — the `#[check_providers(...)]` form: the trait supertraits `IsProviderFor` and is implemented for each listed provider rather than for the context. -- [checking/check_path_context.rs](../../../crates/tests/cgp-tests/tests/checking/check_path_context.rs) — a path-qualified context (`inner::Context`): the derived trait name (`__CheckContext`) comes from the final path segment, and the impl targets the context by its full path. - -No snapshot pins the plainest single-block, single-bare-component case on its own; it is covered implicitly by the richer `check_trait` block above. - -## Tests - -The behavioral coverage for `check_components!` is the compile-time assertion itself: - -- The files listed under Snapshots are compile-only tests, so a successful build is the passing check. Each pins both the expansion (via the snapshot) and the fact that the asserted wiring resolves. -- [parser_rejections/check_components.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/check_components.rs) — the table-level attribute rejections: an empty `#[check_providers()]`, a repeated `#[check_providers]`, a repeated `#[check_trait]`, and an unknown attribute each fail to parse. -- The `cargo-cgp` UI fixture [`acceptable/fields/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/missing_dependency.rs) pins that an unsatisfied impl-side dependency is reported with its `E0277` caret on the checked component inside the block — a regression test for the `override_span` re-span (see [Failure modes](#failure-modes)). Its error anatomy is the [check-trait failure](../../errors/checks/check-trait-failure.md) class. -- The `cargo-cgp` UI fixtures [`acceptable/resolution/ordinary_bound_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/resolution/ordinary_bound_unsatisfied.rs) and [`acceptable/generic/generic_context_ordinary_bound.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/generic/generic_context_ordinary_bound.rs) pin a check surfacing an *ordinary* trait bound (`Scalar: Eq`, unmet by a wired `f64`) as the primary `E0277`, from a concrete context and from a generic ` Wrapper` table checked at `Wrapper` respectively. Their error anatomy is the [unsatisfied ordinary trait bound](../../errors/checks/ordinary-trait-bound.md) class. - -## Source - -- Entry point: `check_components` in [cgp-macro-lib/src/check_components.rs](../../../crates/macros/cgp-macro-lib/src/check_components.rs). -- Tables, entries, keys, and values: [cgp-macro-core/src/types/check_components/](../../../crates/macros/cgp-macro-core/src/types/check_components/), documented together with the `delegate_and_check_components!` stack in [asts/check_components.md](../asts/check_components.md). -- The check trait, the `#[check_trait]`/`#[check_providers]` attributes, the `__Check{Context}` name derivation, the supertrait choice, and the span override are all in `table.rs`; the cartesian-product expansion is in `entry.rs`. The span override applies the [`override_span`](../../../crates/macros/cgp-macro-core/src/functions/override_span.rs) helper, which clobbers *every* token's span unconditionally — here that is the point, since the one shared context type is forced onto each checked component in turn. This is the opposite of what `delegate_components!` needs: its sibling `override_item_span` in the same file re-spans only an impl's boundary tokens so a wired provider's own span survives for IDE navigation. -- Fragment construction: [parse_internal!](../macros/parse_internal.md). -- The `delegate_and_check_components!` macro reuses this stack; see its [entrypoint document](delegate_and_check_components.md). diff --git a/docs/implementation/entrypoints/delegate_and_check_components.md b/docs/implementation/entrypoints/delegate_and_check_components.md deleted file mode 100644 index 0ee84362..00000000 --- a/docs/implementation/entrypoints/delegate_and_check_components.md +++ /dev/null @@ -1,86 +0,0 @@ -# `delegate_and_check_components!` — implementation - -`delegate_and_check_components!` wires a context and asserts the wiring in one step, by parsing the shared `DelegateTable`, evaluating its delegation half, and deriving a `CheckComponentsTable` from the delegation keys. This document covers how that fusion works internally; for the accepted syntax and the complete expansion a user sees, read the reference document [reference/macros/delegate_and_check_components.md](../../reference/macros/delegate_and_check_components.md). - -## Entry point - -The macro is driven by the thin `delegate_and_check_components` function in [cgp-macro-lib/src/delegate_and_check_components.rs](../../../crates/macros/cgp-macro-lib/src/delegate_and_check_components.rs). It parses the body into an [`ItemDelegateAndCheckComponents`](../asts/check_components.md) (a wrapper around a `DelegateTable`), derives the check table from it, evaluates the delegation half through the shared `DelegateTable`, and emits both halves: - -```rust -let item: ItemDelegateAndCheckComponents = parse2(body)?; -let check_table = item.to_check_components()?; -let evaluated_table = item.table.eval()?; -let check_items = check_table.to_items()?; -Ok(quote! { #evaluated_table #( #check_items )* }) -``` - -All real logic lives in `cgp-macro-core`. The macro shares both stacks it fuses: the [`delegate_component` stack](../asts/delegate_component.md) for the wiring half and the [`check_components` stack](../asts/check_components.md) for the checking half. - -## Pipeline - -The macro reuses two existing pipelines rather than defining its own. The delegation half is exactly `DelegateTable::eval`, unchanged from [`delegate_components!`](delegate_components.md), so the wiring impls are identical to what that macro emits. The checking half is derived: `to_check_components` reads the delegation keys, converts each into a check entry, and packages them into a `CheckComponentsTable` whose `to_items` is exactly the [`check_components!`](check_components.md) pipeline. The only new work is the key-to-check-entry conversion and the per-entry `#[check_params]`/`#[skip_check]` handling. - -## Generated items - -The macro emits the delegation impls first — a `DelegateComponent` impl and an `IsProviderFor` forwarding impl per entry, exactly as `delegate_components!` produces — then the check trait and one impl per non-skipped entry, exactly as `check_components!` produces. The derived check trait defaults to `__CanUse{Context}` (not `__Check{Context}`), so a `delegate_and_check_components!` and a `check_components!` block can coexist once each in the same module: - -```rust -// delegate_and_check_components! { MyContext { NameGetterComponent: UseField } } -impl DelegateComponent for MyContext { - type Delegate = UseField; -} -impl<__Context__, __Params__> - IsProviderFor for MyContext -where - UseField: IsProviderFor, -{} -// … then the checking half: -trait __CanUseMyContext<__Component__, __Params__: ?Sized>: - CanUseComponent<__Component__, __Params__> -{} -impl __CanUseMyContext for MyContext {} -``` - -A table-level `#[check_trait(Name)]` overrides the derived name. A generic table threads its generics through both halves, since both reuse the same `impl_generics` from the parsed `DelegateTable`. - -## Behavior and corner cases - -**Every delegated key is checked by default.** The conversion walks the delegation entries and produces one bare check entry per key, so wiring an entry with no attribute both delegates and checks it. - -**`#[check_params(...)]` supplies the parameters the check needs.** A component with generic parameters has a parameter-generic `DelegateComponent` impl but a check that needs concrete parameters, so `#[check_params(...)]` provides them: each listed parameter becomes its own check impl, while the single delegation impl stays generic. `#[skip_check]` contributes the delegation impls but no check impl. - -**The two attributes are mutually exclusive and merge across bracket levels.** `#[check_params]` and `#[skip_check]` cannot both apply to one key, and at most one of each may appear. For an array key, a block-level attribute on the bracket merges with each inner key's own attribute — two `#[check_params]` sets union, while combining `#[skip_check]` with `#[check_params]` is an error. - -**A per-key generic list is threaded onto the derived check impl.** A delegation key that introduces its own generic parameters (` FooKey: …`) carries them into the check half, so the generated check impl binds them (`impl __CanUse…, …> for Context {}`) rather than referencing them unbound. `KeyWithCheckParams` attaches the key's generics to every check value it produces: a bare `#[check_params(…)]`-less key gets a unit-params value carrying the generics, and each `#[check_params(…)]` parameter carries them too, so the merge in `CheckComponentsTable::eval` binds them alongside the table-level generics. - -**Statement forms and non-plain keys are not checked.** The `open`/`namespace`/`for` statements and redirect (`=>`) and `@`-path keys still produce delegation impls through `eval`, but the conversion generates no check entries for them; it validates that they carry no attributes rather than silently ignoring one. Path keys on the delegation side are likewise dropped from the check half. This means a per-value `open` dispatch is wired but not checked. - -## Failure modes - -Because the macro reuses the `delegate_components!` and `check_components!` pipelines, its accepted-but-uncompilable inputs are the same ones those macros defer to the compiler. A **duplicate key** produces `E0119` on the wiring side (and, if checked, the check side too) — the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. A **missing impl-side dependency** is what the check half exists to surface — the [check-trait failure](../../errors/checks/check-trait-failure.md) error class, reported at the wiring site rather than lazily at the call site. - -A table whose every entry is `#[skip_check]` (or an empty table) still emits the check trait but no check impls, so it verifies nothing; this parallels the empty `#[check_providers()]` that `check_components!` rejects, but here it is accepted because skipping every entry is a legitimate, if degenerate, request. - -## Snapshots - -Every `snapshot_delegate_and_check_components!` invocation across the suite is indexed here, since these snapshots belong to this entrypoint: - -- [checking/delegate_and_check_basic.rs](../../../crates/tests/cgp-tests/tests/checking/delegate_and_check_basic.rs) — the basic form: two entries wired and checked, with the check trait renamed via `#[check_trait(...)]`. -- [checking/delegate_and_check_generic.rs](../../../crates/tests/cgp-tests/tests/checking/delegate_and_check_generic.rs) — a generic context (` MyContext`) threaded through both halves, with the check trait defaulting to `__CanUse{Context}`. -- [checking/delegate_and_check_params.rs](../../../crates/tests/cgp-tests/tests/checking/delegate_and_check_params.rs) — `#[check_params(...)]` supplying parameter tuples, an array key wiring several components to one provider, and a block-level `#[check_params(...)]` on the bracket merged with each entry's own. -- [checking/delegate_and_check_generic_key.rs](../../../crates/tests/cgp-tests/tests/checking/delegate_and_check_generic_key.rs) — a delegation key carrying its own generic parameters (` BarGetterAtComponent`) whose generics are threaded onto the derived check impl, with a `#[check_params((I, Index<0>))]` value that mentions the key generic. -- [dispatching/use_delegate_getter.rs](../../../crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs) — a `UseDelegate`-table value wired and checked in one step, exercising the legacy nested-table form through this macro. - -One variant has no snapshot: a `#[skip_check]` entry alongside checked entries, which the reference shows but no snapshot pins. - -## Tests - -- The snapshot files above are compile-only tests, so a successful build is the passing assertion for both the wiring and the derived check. -- There are no separate behavioral or `cgp-macro-tests` failure cases for this macro. - -## Source - -- Entry point: `delegate_and_check_components` in [cgp-macro-lib/src/delegate_and_check_components.rs](../../../crates/macros/cgp-macro-lib/src/delegate_and_check_components.rs). -- Wrapper item, key-to-check conversion, and attribute types: [cgp-macro-core/src/types/delegate_and_check_components/](../../../crates/macros/cgp-macro-core/src/types/delegate_and_check_components/), documented with the `check_components!` stack in [asts/check_components.md](../asts/check_components.md). -- The `__CanUse{Context}` default name and `#[check_trait]` handling are in `item.rs`, the `#[check_params]`/`#[skip_check]` parsing and mutual exclusion in `check_params.rs`, the per-key conversion in `key_with_check_params.rs`, and the walk over delegation entries in `to_keys_with_check_params.rs`. -- Reuses the [`DelegateTable`](../asts/delegate_component.md) for the wiring half and the [`CheckComponentsTable`](../asts/check_components.md) for the checking half. diff --git a/docs/implementation/entrypoints/delegate_components.md b/docs/implementation/entrypoints/delegate_components.md deleted file mode 100644 index 919bcc6f..00000000 --- a/docs/implementation/entrypoints/delegate_components.md +++ /dev/null @@ -1,145 +0,0 @@ -# `delegate_components!` — implementation - -`delegate_components!` builds a context's type-level wiring table by parsing a `DelegateTable` from the macro body and lowering each mapping into a `DelegateComponent` impl plus a forwarding `IsProviderFor` impl. This document covers how that lowering works internally; for the accepted syntax and the complete expansion a user sees, read the reference document [reference/macros/delegate_components.md](../../reference/macros/delegate_components.md). - -## Entry point - -The macro is driven by the thin `delegate_components` function in [cgp-macro-lib/src/delegate_components.rs](../../../crates/macros/cgp-macro-lib/src/delegate_components.rs). It parses the whole body into a single [`DelegateTable`](../asts/delegate_component.md), rejects any attributes the parser accepted (the table supports none), evaluates the table, and emits the resulting tokens: - -```rust -let table: DelegateTable = parse2(body.clone())?; -table.validate_attributes()?; -let evaluated_table = table.eval()?; -Ok(evaluated_table.to_token_stream()) -``` - -All real logic lives in `cgp-macro-core`. A malformed body fails while parsing `DelegateTable`, and an attribute on the table or any key fails in `validate_attributes` with a spanned "unsupported attribute" error rather than being silently dropped. The check recurses through mapping values, so an attribute on a key nested inside a `UseDelegate` table is rejected too, not just one on a top-level key. - -## Pipeline - -The macro has two stages after parsing: attribute rejection and a single `eval`. Parsing produces the whole [`delegate_component` AST stack](../asts/delegate_component.md) — the table, its `new` keyword and optional generic list, the entries (statements plus mappings), and the keys and values inside each mapping. `eval` walks that tree once, lowering every mapping and statement into a flat list of evaluated entries and rendering each into its impl pair. The `open`/`namespace`/`for` statements and the nested-`UseDelegate` values are handled inside `eval` as part of the same walk; there is no separate preprocessing stage. - -## Generated items - -For every table entry the macro emits two impls in order: a `DelegateComponent` impl that records the mapping (the component key, the chosen provider as the `Delegate` type) and an `IsProviderFor` impl that forwards the provider's dependencies back through the table so a missing transitive requirement stays diagnosable. A plain `Key: Provider` mapping lowers directly: - -```rust -// delegate_components! { Rectangle { AreaCalculatorComponent: RectangleArea } } -impl DelegateComponent for Rectangle { - type Delegate = RectangleArea; -} -impl<__Context__, __Params__> - IsProviderFor for Rectangle -where - RectangleArea: IsProviderFor, -{} -``` - -Both `__Context__` and `__Params__` are the reserved identifiers that appear literally in the output. When the body carries a leading `new` keyword, the macro additionally emits the target struct (`struct Rectangle;`, or a generic struct if the target carries parameters), and a nested-`UseDelegate` value lifts its inner table out into its own struct and impls, so a value like `UseDelegate` contributes both the outer entry and a full inner table. - -The `open` header and `@Component.Key` entries lower through the [`RedirectLookup`](cgp_component.md) impl that every `#[cgp_component]` already generates. The header wires each opened component to a redirect rooted at the component name in the context's own table, and each `@`-path entry stores its provider under the extended path key: - -```rust -// open AreaCalculatorComponent; → the redirect entry -impl DelegateComponent for MyApp { - type Delegate = RedirectLookup>; -} -// @AreaCalculatorComponent.Rectangle: RectangleArea → a keyed entry on the same table, -// keyed by the redirect path with a trailing wildcard, mapping to RectangleArea -``` - -The per-value entries are ordinary `DelegateComponent`/`IsProviderFor` pairs whose key is the redirect path type; `RedirectLookup` appends the dispatch parameter onto the path at lookup time and reads the result back. - -## Behavior and corner cases - -A **mapping operator** selects which value lowering applies. `:` (Normal) maps the key straight to the named provider; `->` (Direct) sets the `Delegate` to `>::Delegate` and adds a `Value: DelegateComponent` bound, so the entry forwards to the value's own entry for that key; `=>` (Redirect) sets the `Delegate` to `RedirectLookup` along an `@`-path value. The [`delegate_component` AST document](../asts/delegate_component.md) describes each in full. - -An **array key** `[A, B]: Provider` expands to one impl pair per bracketed key, all pointing at the same value, because the key evaluates to a vector of evaluated keys that the mapping iterates. A **per-key or per-table generic list** is merged onto every generated impl: a table-level `<'a, T>` is threaded through each impl's generics, and a key may introduce its own extra generics (` BazKey`) that merge with the table's. - -An **`@`-path key** carries a leading `__Wildcard__` generic and lowers the path to a prefix type ending in that wildcard, which is how a dispatch parameter slots in at lookup time. A **brace group on a path segment** (`@Component.{u32, u64}: P`) expands to one key per element, and the `namespace`/`for` statement forms lower through a shared "for-entry" path that builds a `Namespace<…, Delegate = …>` bound rather than a direct `DelegateComponent` impl; these are the namespace machinery and are detailed in the AST document. A **`for` loop's optional `where` clause** is merged into every impl the loop generates, alongside that reconstructed bound, so a bound written on the loop constrains which keys it wires. - -## Error spans - -Each generated impl is re-spanned onto the entry that produced it, so a compiler error about that impl points at the entry the user wrote rather than at the whole `delegate_components!` block. The impls are built with `parse_internal!`, which quasi-quotes their tokens; those tokens carry the macro invocation's `call_site` span, and the only tokens with a narrower span are the interpolated key, value, and target type. A coherence conflict (`E0119`) between two entries mapping the same key is reported on the impl header — its `impl` keyword and trait reference — which is exactly the part that starts at `call_site`, so without correction the error underlines the entire block, and two overlapping entries produce two block-wide carets that say nothing about which entry to fix. - -`build_delegate_component_impl` and `build_is_provider_for_impl` fix this through `respan_impl`, which re-spans only the impl's two *boundary* tokens — its leading `impl` keyword and its trailing `{ … }` body — onto the entry's diagnostic span, and leaves everything between them alone. That is enough because the compiler derives a generated item's span, and therefore the `E0119` caret, by joining its first and last tokens (`first.to(last)`): with the whole impl at `call_site` the caret is the whole invocation, and pulling just those two ends onto the entry collapses it onto what the user wrote. The interior — the trait reference, the reserved `__Context__`/`__Params__` generics, the key, the wired provider, the target type, a per-entry generic — keeps its own spans. - -Leaving the interior in place is not just an economy; it is what keeps the result usable in two ways the whole-impl re-span could not. A diagnostic about a per-entry generic still points where the user declared it — an unconstrained parameter's `E0207` lands on the `` the user wrote rather than being dragged onto the key — because that `` is an interior token the re-span never touches. And a type written in the block stays navigable: rust-analyzer maps a source token to its expanded counterpart purely by source range, ignoring hygiene, so a synthesized reference like `IsProviderFor` or `DelegateComponent` re-spanned onto the key would land on the key's exact range, and go-to-definition on that key would then offer every such collided construct as a candidate. Keeping the interior untouched leaves each synthesized reference at `call_site` (a range no narrower user token shares) and each user token — the wired provider, the target type — at its own span, so navigation resolves to the one right definition. The two tokens that *do* move are a keyword and a delimiter group, never references, so moving them onto the key misleads neither the compiler nor the editor. (An earlier fix re-spanned *every* token of the impl onto the entry; that put `IsProviderFor` and the target type on the key's range and broke go-to-definition, which is why the re-span is now scoped to the boundary.) The [`override_item_span`](../../../crates/macros/cgp-macro-core/src/functions/override_span.rs) helper it uses re-spans a boundary token only when that token is itself synthesized, recognized by its `call_site` span. `check_components!` aims its own error the opposite way, with the unconditional `override_span` helper in the same file, because there the intent *is* to clobber a user token — one shared context type, forced onto each checked component in turn. - -The diagnostic span is carried explicitly rather than read back from the generated key, because a key type may be *synthesized* and so no longer carry a useful span. This follows the [`EvaluatedCheckEntry.span`](check_components.md) pattern: `EvaluatedDelegateKey`, `EvaluatedDelegateEntry`, and the `namespace`/`for` intermediary `EvaluatedForEntry` each hold a `span` field, populated at eval time from the token the user actually wrote and threaded through to `respan_impl`. Every entry form sources its span this way, so none falls back to `call_site`: a plain `Key: Provider`, `Key -> Value`, or `Key => @path` mapping (and each element of an array key) takes the key type's own span; the `open` header takes the opened component's span; a `namespace`/`for` statement takes the namespace name or the loop mapping's key; and a nested `UseDelegate` table lowers through the same per-entry path, so its inner keys are spanned too. - -An `@`-path key needs more than its lowered type, since that type is a `PathCons<..>` nest whose first token is a `call_site`-spanned `PathCons` (the same problem `Symbol` avoids by keeping a parse-time span so its synthesized `Chars`/`Symbol` tokens do not fall back to `call_site`). So an `@`-path sources its span from the path segments — the join of the segment spans, which widens to the whole path where the toolchain supports `Span::join` and otherwise keeps the leaf segment (the component of a namespace path, or the dispatch key of an `@Component.key` entry), so the caret lands on the segment that discriminates entries sharing a prefix. - -Because the `.stderr` fixtures record the exact line and column of each caret, they double as regression tests for these spans: reverting the re-span snaps the carets back to the block and the snapshots change (see [Tests](#tests)). - -## Failure modes - -Some `delegate_components!` inputs are accepted by the macro but fail to compile downstream, because the macro lowers each block independently with no whole-program view. These are intended behavior rather than bugs, and their full anatomy is documented in the [error catalog](../../errors/README.md); the [Error spans](#error-spans) section below covers how each caret is aimed at the offending entry. - -A **duplicate key** (the same component mapped twice — across blocks, within a block, or an `open` header colliding with a mapping) and an **overlapping generic entry** (a ` Wrapper` table conflicting with a specific `Wrapper`) both produce the coherence error `E0119`, the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. - -A **missing impl-side dependency** — a lazily-wired provider whose `where` clause the context cannot satisfy — is accepted here and fails only when the consumer trait is used. Its full anatomy (the `E0599` that names `Person: Greeter` while *hiding* the missing dependency, and how a `check_components!` site promotes it to a readable error) is documented as the [hidden unsatisfied-dependency](../../errors/hidden/unsatisfied-dependency.md) error class in the error catalog. - -An **unconstrained per-entry generic** — a parameter that reaches the provider value but not the key — is rejected with `E0207`, the [unconstrained generic](../../errors/wiring/unconstrained-generic.md) error class. - -## Known issues - -The macro's parser is permissive about the body shape and surfaces most mistakes as generic `syn` parse errors rather than tailored diagnostics — for example, an `open` header written after a plain mapping fails to parse because statements must lead the block, but the error (`expected `:``) points at the unexpected token rather than explaining the ordering rule. - -## Snapshots - -Every `snapshot_delegate_components!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint. The basic-delegation snapshots pin the plain-table forms: - -- [basic_delegation/delegate_components_macro.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/delegate_components_macro.rs) — the canonical `new`-table expansion (two entries) plus the `->` forwarding form that delegates to another table's entry. -- [basic_delegation/delegate_array_key.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/delegate_array_key.rs) — an array key expanding to one impl pair per bracketed key. -- [basic_delegation/delegate_generic_table.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/delegate_generic_table.rs) — a leading `<'a, T1: Clone>` generic list threaded onto every impl, with a key introducing its own extra generic (` BazKey`). - -The namespace snapshots pin the statement and `@`-path forms: - -- [namespaces/open_dispatch.rs](../../../crates/tests/cgp-tests/tests/namespaces/open_dispatch.rs) — the braced `open { A, B }` header opening two components at once, plus `@Component.Key` per-value entries, including a brace group sharing one provider across several keys. -- [namespaces/multi_param_open.rs](../../../crates/tests/cgp-tests/tests/namespaces/multi_param_open.rs) — the braceless single-component `open Component;` form, dispatched on a multi-segment `@Component.A.B` path with one segment carrying an entry generic. -- [namespaces/namespace_basic.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_basic.rs), [namespaces/namespace_symbol_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_symbol_path.rs), [namespaces/namespace_type_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_type_path.rs) — the `namespace …;` header forwarding every lookup through a namespace trait, with bare, symbol-path, and type-path `@`-keys. -- [namespaces/namespace_multi.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_multi.rs), [namespaces/namespace_group.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_group.rs) — brace-group and array-group `@`-keys expanding to the cartesian product of segments. -- [namespaces/multi_param_namespace.rs](../../../crates/tests/cgp-tests/tests/namespaces/multi_param_namespace.rs) — multi-segment namespace paths with a per-segment generic. -- [namespaces/extended_namespace_wiring.rs](../../../crates/tests/cgp-tests/tests/namespaces/extended_namespace_wiring.rs) — a namespace table mixing plain and nested-group `@`-paths across several crates' components. -- [namespaces/prefix_default_namespace.rs](../../../crates/tests/cgp-tests/tests/namespaces/prefix_default_namespace.rs) — a `DefaultNamespace` header with fully-qualified `@cgp.core.error.…` paths. -- [namespaces/default_impls_wiring.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs) — the `for in SomeTable { … }` loop form pulling entries from another lookup table. -- [namespaces/for_where_clause.rs](../../../crates/tests/cgp-tests/tests/namespaces/for_where_clause.rs) — the `for <..> in .. where ..` loop with a `where` clause, pinning the clause merged onto each generated impl beside the reconstructed namespace bound. -- [namespaces/redirect_lookup.rs](../../../crates/tests/cgp-tests/tests/namespaces/redirect_lookup.rs) — a `namespace` header producing the `RedirectLookup`-style blanket `DelegateComponent` impl. -- [dispatching/use_delegate_getter.rs](../../../crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs) — the legacy `UseDelegate` nested-table value, including a custom `UseDelegate2` wrapper over tuple keys. - -One variant has no snapshot: a bare (non-`new`) single-entry table with a plain type target, distinct from the standalone `new` bundle that owns the canonical snapshot. - -## Tests - -The behavioral tests confirm the generated wiring resolves and compiles: - -- [basic_delegation/delegate_new_struct.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/delegate_new_struct.rs) checks that `new` declares the table struct and the table resolves as written. -- [basic_delegation/delegate_new_array_key.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/delegate_new_array_key.rs) checks the array-key and nested-`new` forms parse and expand together. -- [basic_delegation/delegate_new_generic_struct.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/delegate_new_generic_struct.rs) checks that ` new MyComponents` declares a generic table struct. -- [basic_delegation/delegate_nested_use_delegate.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/delegate_nested_use_delegate.rs) checks a two-level nested `UseDelegate` value builds an inline dispatch table. -- [basic_delegation/delegate_generic_nested_value.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/delegate_generic_nested_value.rs) checks a per-entry `` list threads through both the outer key and the inner generated table struct. -- [basic_delegation/consumer_delegate_getter.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_getter.rs) and [basic_delegation/consumer_delegate_generic.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_generic.rs) check that a context may satisfy some components by wiring and others by a direct trait impl, and that a generic component resolves independently per type argument. - -The failure cases in `cgp-macro-tests` pin the attribute rejection: - -- [parser_rejections/delegate_components.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs) asserts the macro rejects an attribute on the table, on a key, and on a key nested inside a `UseDelegate` value (the last confirms the validator recurses through mapping values rather than dropping the attribute), and that a braceless `open` header listing more than one component is rejected (the braceless form opens exactly one). - -The post-codegen compile-fail cases are now `cargo-cgp` UI fixtures that pin the expansions that fail to compile. All are **acceptable** failures — deferred to the compiler by design, none a defect — and their anatomy is documented in the [error catalog](../../errors/README.md); the [Failure modes](#failure-modes) section links each to its class. `cargo-cgp` files each fixture by the *quality of the output* it renders (`acceptable/` when the tool already leads with the cause, `usability/` when the cause is present but buried), which is why the unconstrained-generic case below sits under `usability/` even though it is not a defect: - -- [`acceptable/wiring/duplicate-keys/duplicate_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_key.rs) — two blocks mapping the same key expand to conflicting `DelegateComponent` impls (`E0119`). -- [`acceptable/wiring/duplicate-keys/duplicate_key_same_block.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_key_same_block.rs) — the same conflict from two entries in one block; its `.rust.stderr` pins the per-entry [error spans](#error-spans), each caret landing on its own `GreeterComponent` key rather than the whole block. -- [`acceptable/wiring/namespace-paths/delegate_duplicate_path_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/delegate_duplicate_path_key.rs) — the `@`-path analogue: two identical `@cgp.core.error.ErrorTypeProviderComponent` entries under a `namespace` header conflict, and its `.rust.stderr` pins the [error span](#error-spans) landing on the duplicated path leaf rather than the whole block, even though the key lowers to a synthesized `PathCons<..>` type. -- [`acceptable/wiring/duplicate-keys/duplicate_open_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_open_key.rs) — an `open` header colliding with an explicit mapping for the same component; its `.rust.stderr` pins the [error span](#error-spans) of the `open`-header entry, whose span comes from the opened component (a source distinct from the plain key path). -- [`acceptable/wiring/duplicate-keys/overlapping_generic.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/overlapping_generic.rs) — a generic ` Wrapper` entry overlaps a specific `Wrapper` entry at the same key (`E0119`). -- [`acceptable/use-site/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/missing_dependency.rs) — a lazily-wired provider whose `Self: HasName` dependency the context does not satisfy; the unmet bound surfaces at the call site (`E0599`). Its anatomy is the [hidden unsatisfied-dependency](../../errors/hidden/unsatisfied-dependency.md) error class. -- [`acceptable/use-site/ordinary_bound_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/ordinary_bound_unsatisfied.rs) — the same hidden `E0599` where the suppressed dependency is an *ordinary* trait bound (`Scalar: Eq`, unmet by a wired `f64`) rather than a `HasField`, called rather than checked. Same [hidden unsatisfied-dependency](../../errors/hidden/unsatisfied-dependency.md) class; its surfaced counterpart is the [unsatisfied ordinary trait bound](../../errors/checks/ordinary-trait-bound.md) class. -- [`usability/wiring/constraints/unconstrained_generic.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/constraints/unconstrained_generic.rs) — a per-entry generic that appears only in the value (` GreeterComponent: GreetWith`) lowers to an impl with an unconstrained `T` (`E0207`), which the compiler rejects as it would a hand-written impl. - -## Source - -- Entry point: `delegate_components` in [cgp-macro-lib/src/delegate_components.rs](../../../crates/macros/cgp-macro-lib/src/delegate_components.rs). -- The table, its entries, keys, values, and statements: [cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/), documented in [asts/delegate_component.md](../asts/delegate_component.md). -- Attribute rejection is in `validate_attributes.rs`; the impl pair is built in `mapping/eval.rs`. -- Fragment construction: [parse_internal!](../macros/parse_internal.md). -- The `open` and `@`-path forms build on the [`RedirectLookup`](cgp_component.md) impl that `#[cgp_component]` generates. diff --git a/docs/implementation/entrypoints/derive_build_field.md b/docs/implementation/entrypoints/derive_build_field.md deleted file mode 100644 index 019f87eb..00000000 --- a/docs/implementation/entrypoints/derive_build_field.md +++ /dev/null @@ -1,64 +0,0 @@ -# `#[derive(BuildField)]` — implementation - -`#[derive(BuildField)]` emits just the incremental-builder slice of the record machinery: a `__Partial{Name}` companion struct plus the `HasBuilder`, `IntoBuilder`, `PartialData`, `FinalizeBuild`, per-field `UpdateField`, and per-field `HasField` impls that assemble a struct one field at a time. This document covers how that codegen works; for the accepted syntax and the full expansion, read the reference document [reference/derives/derive_build_field.md](../../reference/derives/derive_build_field.md). - -## Entry point - -The macro is driven by the `derive_build_field` function in [cgp-macro-lib/src/derive_build_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_build_field.rs). It parses the input into a `syn::ItemStruct`, wraps it in an `ItemCgpRecord`, and calls `to_build_field_items` — the same method the record path of `#[derive(CgpData)]` uses for its builder slice: - -```rust -let record = ItemCgpRecord { item_struct }; -let items = record.to_build_field_items()?; -``` - -Applying the derive to a non-struct item fails at `syn::parse2`. - -## Pipeline - -There is no multi-stage transform. `ItemCgpRecord::to_build_field_items` names the companion struct `__Partial{ContextName}` and composes the helpers in the [`derive_builder/`](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/) submodule in a fixed order: `derive_builder_struct`, `derive_has_builder_impl`, `derive_into_builder_impl`, `derive_partial_data_impl_from_struct`, `derive_finalize_build_impl`, `derive_update_field_impls`, and `derive_has_field_impls`. The [`cgp_data` AST stack](../asts/cgp_data.md) documents `ItemCgpRecord` and the field-tag types. - -## Generated items - -The derive centers on the partial companion struct `__Partial{Name}`: a clone of the input struct that gains one `MapType` type parameter per field and wraps each field type in that parameter's `Map`. The marker decides how a field is stored — `IsPresent` maps `T` to `T`, `IsNothing` maps it to the unit `()`, and `IsVoid` maps it to the empty `Void` — so a field's present/absent state is encoded in the type: - -```rust -pub struct __PartialPerson<__F0__: MapType, __F1__: MapType> { - pub first_name: <__F0__ as MapType>::Map, - pub last_name: <__F1__ as MapType>::Map, -} -``` - -Around that struct the derive emits the entry and exit points. `HasBuilder` starts an empty builder at `__PartialPerson`; `IntoBuilder` turns an existing value into a fully-present builder at ``; `PartialData` records that any configuration targets the original struct; and `FinalizeBuild` reconstructs the struct but is implemented only for the all-`IsPresent` configuration — so an incomplete build cannot be finalized. It then emits, per field, an `UpdateField` impl that flips one field's marker to `M` and returns the old value alongside the rebuilt partial struct, and a `HasField` impl on the partial type that is in scope only when that field's marker is `IsPresent`: - -```rust -impl<__F1__: MapType> HasField for __PartialPerson { - type Value = String; - fn get_field(&self, _: PhantomData) -> &String { &self.first_name } -} -``` - -The `BuildField` trait itself is *not* emitted. It is a blanket impl in the field crate over any `UpdateField`, so `build_field` is sugar over the generated `update_field` that transitions a field from absent to present; `FinalizeBuild` is likewise a field-crate subtrait of `PartialData`, and the derive supplies only the all-present impl. - -## Behavior and corner cases - -A named field is keyed by [`Symbol!`](../../reference/macros/symbol.md) and a tuple field by [`Index`](../../reference/types/index.md), following the whole family's tagging rule, so a tuple struct produces `UpdateField, _>` impls. The struct's generic parameters and `where` clause are preserved and carried onto the companion struct and every impl, with the field markers added on top and `PartialData::Target` naming the original struct with its own generics. - -An **empty struct** produces a `__Partial{Name}` with no field markers, a trivial `HasBuilder`/`FinalizeBuild`, and no `UpdateField`/`HasField` impls. This derive emits no `HasField`/`HasFieldMut` getters on the original struct and no `HasFields` representation impls — those come from [`#[derive(HasField)]`](derive_has_field.md) and [`#[derive(HasFields)]`](derive_has_fields.md); `BuildField` is purely the construction slice, included wholesale by [`#[derive(CgpRecord)]`](derive_cgp_record.md) and [`#[derive(CgpData)]`](derive_cgp_data.md). - -## Error spans - -Each generated impl is re-spanned onto the token it derives from, so a compiler error points at that token rather than at the whole `#[derive(BuildField)]`. The per-field `UpdateField` and `HasField`-on-partial impls are aimed at the field they read (the field identifier, or the whole `syn::Field` for a tuple field), and the whole-struct `HasBuilder`/`IntoBuilder`/`PartialData`/`FinalizeBuild` impls at the struct name. Each goes through [`override_item_span`](../README.md#spans-aim-generated-items-at-the-token-the-user-wrote), moving only the `impl`/`{ … }` boundary — the mechanism the [`#[derive(HasField)]`](derive_has_field.md#error-spans) doc explains in full. The `__Partial{Name}` companion struct is cloned from the user's own struct, so its tokens already carry meaningful spans and need no re-spanning. - -## Tests - -`#[derive(BuildField)]` has no snapshot macro of its own; the builder items it emits are part of the record expansion pinned by the `snapshot_derive_cgp_data!` snapshots indexed in [derive_cgp_data.md's Snapshots section](derive_cgp_data.md#snapshots). The behavioral builder tests in [crates/tests/cgp-tests/tests/extensible_records/](../../../crates/tests/cgp-tests/tests/extensible_records/) exercise the machinery: - -- [record_build_from.rs](../../../crates/tests/cgp-tests/tests/extensible_records/record_build_from.rs) drives `builder`/`build_from`/`build_field`/`finalize_build`. -- [optional_builder.rs](../../../crates/tests/cgp-tests/tests/extensible_records/optional_builder.rs) drives the optional-builder path. -- [point_cast.rs](../../../crates/tests/cgp-tests/tests/extensible_records/point_cast.rs) drives the `build_with_default` cast. - -## Source - -- Entry point: `derive_build_field` in [cgp-macro-lib/src/derive_build_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_build_field.rs). -- Codegen: `ItemCgpRecord::to_build_field_items` in [cgp-macro-core/src/types/cgp_data/record.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/record.rs), which composes the [derive_builder/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/) helpers (`builder_struct.rs`, `has_builder_impl.rs`, `into_builder_impl.rs`, `partial_data.rs`, `finalize_build_impl.rs`, `update_field_impls.rs`, `has_field_impls.rs`); the AST types are documented in [asts/cgp_data.md](../asts/cgp_data.md). -- The `BuildField`, `FinalizeBuild`, `UpdateField`, `HasBuilder`, `IntoBuilder`, and `PartialData` traits and the `MapType` markers live under [crates/core/cgp-field/src/](../../../crates/core/cgp-field/src/). diff --git a/docs/implementation/entrypoints/derive_cgp_data.md b/docs/implementation/entrypoints/derive_cgp_data.md deleted file mode 100644 index d88ae149..00000000 --- a/docs/implementation/entrypoints/derive_cgp_data.md +++ /dev/null @@ -1,76 +0,0 @@ -# `#[derive(CgpData)]` — implementation - -`#[derive(CgpData)]` is the umbrella extensible-data derive: applied to a struct it emits the full record machinery, applied to an enum the full variant machinery, dispatching on shape and reusing the same codegen as the shape-specific `#[derive(CgpRecord)]` and `#[derive(CgpVariant)]`. This document covers how that composition works; for the accepted syntax and the full expansion, read the reference document [reference/derives/derive_cgp_data.md](../../reference/derives/derive_cgp_data.md). - -## Entry point - -The macro is driven by the `derive_cgp_data` function in [cgp-macro-lib/src/cgp_data.rs](../../../crates/macros/cgp-macro-lib/src/cgp_data.rs). It parses the input into an `ItemCgpData` and calls `to_items`: - -```rust -let data: ItemCgpData = parse2(body)?; -let items = data.to_items()?; -``` - -`ItemCgpData` is an enum of `Record(ItemCgpRecord)` or `Variant(ItemCgpVariant)`; its `Parse` impl routes a `struct` to the record arm and an `enum` to the variant arm, rejecting anything else with "expect body to be either a struct or enum". Its `to_items` forwards to the wrapped type's `to_items`, so `CgpData` on a struct emits exactly what `#[derive(CgpRecord)]` emits and `CgpData` on an enum exactly what `#[derive(CgpVariant)]` emits — the two shape-specific derives are `CgpData` restricted to one shape. See the [`cgp_data` AST stack](../asts/cgp_data.md) for those types. - -## Pipeline - -There is no multi-stage transform; the composition happens inside the two `to_items` methods. - -- **`ItemCgpRecord::to_items`** (the struct path) concatenates three slices in order: the per-field `HasField`/`HasFieldMut` getters (the [`#[derive(HasField)]`](derive_has_field.md) output), the five representation impls (the [`#[derive(HasFields)]`](derive_has_fields.md) output), and the incremental builder items (the [`#[derive(BuildField)]`](derive_build_field.md) output). -- **`ItemCgpVariant::to_items`** (the enum path) concatenates three slices in order: the five representation impls over a sum (the enum path of [`#[derive(HasFields)]`](derive_has_fields.md)), the per-variant `FromVariant` constructors (the [`#[derive(FromVariant)]`](derive_from_variant.md) output), and the incremental extractor items (the [`#[derive(ExtractField)]`](derive_extract_field.md) output). - -Each slice is documented in the entrypoint linked beside it; because `CgpData` reuses those exact methods, this document does not repeat the item shapes and points to them instead. - -## Generated items - -For a struct, the fixed emission order is: the `HasField`/`HasFieldMut` getters per field, then `HasFields`, `HasFieldsRef`, `FromFields`, `ToFields`, `ToFieldsRef`, then the builder block — the `__Partial{Name}` struct, `HasBuilder`, `IntoBuilder`, `PartialData`, `FinalizeBuild`, then the per-field `UpdateField` and `HasField` impls. For an enum: `HasFields`, `HasFieldsRef`, `FromFields`, `ToFields`, `ToFieldsRef`, then one `FromVariant` per variant, then the extractor block — the `__Partial{Name}` and `__PartialRef{Name}` enums, `PartialData` for each, `HasExtractor`/`HasExtractorRef`/`HasExtractorMut`, `FinalizeExtract` for each, then the per-variant `ExtractField` impls for both. - -The two views this composes are the *representation* view (the `HasFields` product or sum, convertible with `FromFields`/`ToFields`) and the *incremental* view (the `__Partial…` companion type that tracks per-field presence or per-variant possibility in its type parameters). The reserved companion names are `__Partial{Name}` and, for the borrowed extractor, `__PartialRef{Name}`. The full item shapes live in the building-block entrypoint documents linked above. - -## Behavior and corner cases - -Field tagging follows the same rule as the whole family: a named struct field or an enum variant is keyed by [`Symbol!`](../../reference/macros/symbol.md), and a tuple-struct field by [`Index`](../../reference/types/index.md), so a tuple-struct record exposes `Field, _>` entries and `UpdateField, _>` impls rather than symbol tags. The type's generic parameters and `where` clause are threaded onto every generated impl and onto the `__Partial…` companion types. - -The shape-specific corner cases are inherited from the building blocks rather than introduced here: a single-field tuple struct is special-cased in the `HasFields` product (see [`derive_has_fields`](derive_has_fields.md)), and an enum whose variants are not each single-unnamed-field tuple variants fails in the extractor and `FromVariant` codegen (see [`derive_extract_field`](derive_extract_field.md) and [`derive_from_variant`](derive_from_variant.md)). `CgpData` on such an enum therefore fails the same way, because it runs the same helpers. - -Error spans are inherited the same way. Because `CgpData` runs exactly the slice helpers, each generated impl is already re-spanned onto the token it derives from — a per-field impl onto its field, a per-variant impl onto its variant, and a whole-type impl onto the struct or enum name — so a coherence conflict points at that token rather than at the whole `#[derive(CgpData)]`. The mechanism is documented under [`#[derive(HasField)]`](derive_has_field.md#error-spans) and repeated in each slice's Error spans section. - -## Snapshots - -Every `snapshot_derive_cgp_data!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint. The record expansion is owned by the `extensible_records` target and the variant expansion by `extensible_variants`: - -- [extensible_records/record_derive.rs](../../../crates/tests/cgp-tests/tests/extensible_records/record_derive.rs) — the canonical named-field record expansion (getters, representation, builder). -- [extensible_records/person_record.rs](../../../crates/tests/cgp-tests/tests/extensible_records/person_record.rs) — multi-character field names, pinning the full `Symbol>` spine with its length prefix. -- [extensible_records/tuple_record.rs](../../../crates/tests/cgp-tests/tests/extensible_records/tuple_record.rs) — a tuple struct, the whole record spine keyed by `Index`. -- [extensible_records/generic_record.rs](../../../crates/tests/cgp-tests/tests/extensible_records/generic_record.rs) — a generic record with a `where` clause threaded through every impl. -- [extensible_records/optional_builder.rs](../../../crates/tests/cgp-tests/tests/extensible_records/optional_builder.rs) — the record expansion under the optional-builder runtime path. -- [extensible_records/point_cast.rs](../../../crates/tests/cgp-tests/tests/extensible_records/point_cast.rs) — the record expansion behind a structural record cast with `build_with_default`. -- [extensible_variants/derive_cgp_data.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data.rs) — the canonical concrete-enum variant expansion (representation, `FromVariant`, extractor). -- [extensible_variants/derive_cgp_data_generic.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_generic.rs) — a generic enum, generics lifted onto the `__Partial*` extractor enums, with upcast/downcast. -- [extensible_variants/derive_cgp_data_shape.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_shape.rs) — an enum with struct payloads and multi-character variant names. -- [extensible_variants/derive_cgp_data_empty.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_empty.rs) — a variantless enum, the degenerate special case: bare `__Partial*` enums with no parameters and `match *self {}` in the borrowed accessors. - -## Tests - -The snapshot tests above also carry runtime assertions that exercise the composed machinery: - -- `person_record.rs` builds an `Employee` from a `Person` via the builder. -- `optional_builder.rs` drives the optional builder (`set`/`finalize_optional`/`finalize_with_default`). -- `point_cast.rs` casts a smaller record up into a larger one. -- The `derive_cgp_data*` variant snapshots run the extractor and the upcast/downcast casts. - -Standalone behavioral tests (no snapshot, since the expansion shape is already pinned above) cover the corner cases the snapshots do not: - -- [extensible_records/record_lifetime.rs](../../../crates/tests/cgp-tests/tests/extensible_records/record_lifetime.rs) — the builder path on a struct with a lifetime parameter, threading `'a` through the `__Partial…` companion and every builder impl (the record-side counterpart of the enum lifetime test below). -- [extensible_variants/derive_cgp_data_lifetime.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_lifetime.rs) — the extractor path on an enum whose own lifetime is named `'a`, confirming the reserved `'__a__` borrow lifetime does not collide. -- [record_build_from.rs](../../../crates/tests/cgp-tests/tests/extensible_records/record_build_from.rs), [record_build_with_handlers.rs](../../../crates/tests/cgp-tests/tests/extensible_records/record_build_with_handlers.rs), [shape_dispatch.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch.rs), [shape_dispatch_ref.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch_ref.rs), and [variant_dispatch.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/variant_dispatch.rs) exercise the builder and dispatch behaviors on `CgpData` types. - -The umbrella's own rejection path is pinned in [cgp-macro-tests/parser_rejections/derive_cgp_data.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/derive_cgp_data.rs): the shape dispatch refuses an item that is neither a struct nor an enum (a union), and the enum path propagates the single-unnamed-field variant restriction it shares with [`#[derive(CgpVariant)]`](derive_cgp_variant.md) and [`#[derive(FromVariant)]`](derive_from_variant.md). - -## Source - -- Entry point: `derive_cgp_data` in [cgp-macro-lib/src/cgp_data.rs](../../../crates/macros/cgp-macro-lib/src/cgp_data.rs). -- Shape dispatch: `ItemCgpData` in [cgp-macro-core/src/types/cgp_data/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/item.rs), documented in [asts/cgp_data.md](../asts/cgp_data.md). -- The record path is `ItemCgpRecord::to_items` in `record.rs` (see [`derive_cgp_record`](derive_cgp_record.md)) and the variant path `ItemCgpVariant::to_items` in `variant.rs` (see [`derive_cgp_variant`](derive_cgp_variant.md)), both under [cgp-macro-core/src/types/cgp_data/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/). -- The runtime traits live in [crates/core/cgp-field/src/](../../../crates/core/cgp-field/src/). diff --git a/docs/implementation/entrypoints/derive_cgp_record.md b/docs/implementation/entrypoints/derive_cgp_record.md deleted file mode 100644 index 68d6799e..00000000 --- a/docs/implementation/entrypoints/derive_cgp_record.md +++ /dev/null @@ -1,32 +0,0 @@ -# `#[derive(CgpRecord)]` — implementation - -`#[derive(CgpRecord)]` is `#[derive(CgpData)]` restricted to structs: it emits the full extensible-record machinery — the per-field getters, the representation impls, and the incremental builder — and refuses non-struct input. This document covers the entry point and its shared codegen; for the accepted syntax and the full expansion, read the reference document [reference/derives/derive_cgp_record.md](../../reference/derives/derive_cgp_record.md). - -## Entry point - -The macro is driven by the `derive_cgp_record` function in [cgp-macro-lib/src/cgp_record.rs](../../../crates/macros/cgp-macro-lib/src/cgp_record.rs). It parses the input into an `ItemCgpRecord` and calls `to_items`: - -```rust -let record: ItemCgpRecord = parse2(body)?; -let items = record.to_items()?; -``` - -`ItemCgpRecord::Parse` parses a `syn::ItemStruct`, so applying the derive to an enum or other item fails at parse time — this is the only behavioral difference from `#[derive(CgpData)]` on a struct, which reaches the same `to_items` through `ItemCgpData`. - -## Pipeline and generated items - -There is no multi-stage transform. `ItemCgpRecord::to_items` concatenates three slices in a fixed order — the per-field `HasField`/`HasFieldMut` getters, the five representation impls (`HasFields`, `HasFieldsRef`, `FromFields`, `ToFields`, `ToFieldsRef`), and the incremental builder items (`__Partial{Name}`, `HasBuilder`, `IntoBuilder`, `PartialData`, `FinalizeBuild`, then per-field `UpdateField` and `HasField`). These are exactly the outputs of [`#[derive(HasField)]`](derive_has_field.md), [`#[derive(HasFields)]`](derive_has_fields.md), and [`#[derive(BuildField)]`](derive_build_field.md) respectively; this document does not repeat their item shapes. - -The record's corner cases — `Symbol!` versus `Index` tagging, the newtype `HasFields` special case, and generic threading — are the same as those building blocks describe, because `CgpRecord` runs the same helpers. Error spans are inherited the same way: each generated impl is already re-spanned onto the token it derives from (a per-field impl onto its field, a whole-struct impl onto the struct name), so a coherence conflict points there rather than at the whole `#[derive(CgpRecord)]` — see [`#[derive(HasField)]`](derive_has_field.md#error-spans). The [`cgp_data` AST stack](../asts/cgp_data.md) documents `ItemCgpRecord` and its methods, and [`derive_cgp_data`](derive_cgp_data.md) covers the umbrella derive this is a restriction of. - -## Tests - -`#[derive(CgpRecord)]` has no snapshot macro of its own; its expansion is identical to the record path of `#[derive(CgpData)]` and is pinned by the `snapshot_derive_cgp_data!` snapshots indexed in [derive_cgp_data.md's Snapshots section](derive_cgp_data.md#snapshots). - -- The behavioral record tests in [crates/tests/cgp-tests/tests/extensible_records/](../../../crates/tests/cgp-tests/tests/extensible_records/) — notably [record_build_from.rs](../../../crates/tests/cgp-tests/tests/extensible_records/record_build_from.rs) and [record_build_with_handlers.rs](../../../crates/tests/cgp-tests/tests/extensible_records/record_build_with_handlers.rs) — exercise the builder that this derive produces. - -## Source - -- Entry point: `derive_cgp_record` in [cgp-macro-lib/src/cgp_record.rs](../../../crates/macros/cgp-macro-lib/src/cgp_record.rs). -- Codegen: `ItemCgpRecord::to_items` in [cgp-macro-core/src/types/cgp_data/record.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/record.rs), which composes `derive_has_field_impls_from_struct`, `derive_has_fields_impls_from_struct`, and the [derive_builder/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/) helpers; the AST types are documented in [asts/cgp_data.md](../asts/cgp_data.md). -- The runtime traits live in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/). diff --git a/docs/implementation/entrypoints/derive_cgp_variant.md b/docs/implementation/entrypoints/derive_cgp_variant.md deleted file mode 100644 index 7ce170ca..00000000 --- a/docs/implementation/entrypoints/derive_cgp_variant.md +++ /dev/null @@ -1,35 +0,0 @@ -# `#[derive(CgpVariant)]` — implementation - -`#[derive(CgpVariant)]` is `#[derive(CgpData)]` restricted to enums: it emits the full extensible-variant machinery — the representation impls, the `FromVariant` constructors, and the incremental extractor — and refuses non-enum input. This document covers the entry point and its shared codegen; for the accepted syntax and the full expansion, read the reference document [reference/derives/derive_cgp_variant.md](../../reference/derives/derive_cgp_variant.md). - -## Entry point - -The macro is driven by the `derive_cgp_variant` function in [cgp-macro-lib/src/cgp_variant.rs](../../../crates/macros/cgp-macro-lib/src/cgp_variant.rs). It parses the input into an `ItemCgpVariant` and calls `to_items`: - -```rust -let variant: ItemCgpVariant = parse2(body)?; -let items = variant.to_items()?; -``` - -`ItemCgpVariant::Parse` parses a `syn::ItemEnum`, so applying the derive to a struct or other item fails at parse time — the only behavioral difference from `#[derive(CgpData)]` on an enum, which reaches the same `to_items` through `ItemCgpData`. - -## Pipeline and generated items - -There is no multi-stage transform. `ItemCgpVariant::to_items` concatenates three slices in a fixed order — the five representation impls over a *sum* (`HasFields`, `HasFieldsRef`, `FromFields`, `ToFields`, `ToFieldsRef`), the per-variant `FromVariant` constructors, and the incremental extractor items (the `__Partial{Name}` and `__PartialRef{Name}` enums, `PartialData` for each, `HasExtractor`/`HasExtractorRef`/`HasExtractorMut`, `FinalizeExtract` for each, then the per-variant `ExtractField` impls). These are exactly the outputs of the enum path of [`#[derive(HasFields)]`](derive_has_fields.md), [`#[derive(FromVariant)]`](derive_from_variant.md), and [`#[derive(ExtractField)]`](derive_extract_field.md) respectively; this document does not repeat their item shapes. - -The variant corner cases are inherited from those building blocks: variant names are keyed by [`Symbol!`](../../reference/macros/symbol.md), generics — including a lifetime parameter named `'a` — are threaded onto every impl and onto the partial enums, and every variant must be a single-unnamed-field tuple variant or the extractor and `FromVariant` codegen fail (see [`derive_extract_field`](derive_extract_field.md)). The borrowed extractor introduces its own lifetime under the reserved name `'__a__` precisely so it never collides with the enum's own `'a`. An enum with *no* variants is special-cased so its degenerate expansion still compiles — the borrowed partial enum becomes a bare empty enum and the borrowed accessors match the dereferenced place; see [`derive_extract_field`'s Behavior and corner cases](derive_extract_field.md#behavior-and-corner-cases). Error spans are inherited the same way: each generated impl is already re-spanned onto the token it derives from (a per-variant impl onto its variant, a whole-enum impl onto the enum name), so a coherence conflict points there rather than at the whole `#[derive(CgpVariant)]` — see [`#[derive(HasField)]`](derive_has_field.md#error-spans). The [`cgp_data` AST stack](../asts/cgp_data.md) documents `ItemCgpVariant` and its methods, and [`derive_cgp_data`](derive_cgp_data.md) covers the umbrella derive this is a restriction of. - -## Tests - -`#[derive(CgpVariant)]` has no snapshot macro of its own; its expansion is identical to the enum path of `#[derive(CgpData)]` and is pinned by the `snapshot_derive_cgp_data!` snapshots indexed in [derive_cgp_data.md's Snapshots section](derive_cgp_data.md#snapshots). - -- The behavioral variant tests in [crates/tests/cgp-tests/tests/extensible_variants/](../../../crates/tests/cgp-tests/tests/extensible_variants/) — notably [shape_dispatch.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch.rs), [shape_dispatch_ref.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch_ref.rs), and [variant_dispatch.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/variant_dispatch.rs) — exercise the extractor and constructors this derive produces. -- [derive_cgp_data_lifetime.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_lifetime.rs) derives the machinery on an `enum Message<'a>` and drives the owned, borrowed, and mutable extractors, guarding against the borrowed extractor's lifetime colliding with the enum's own `'a`. -- [derive_cgp_data_empty.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_empty.rs) snapshots the variantless-enum expansion, pinning the empty-enum special case. -- [parser_rejections/derive_from_variant.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/derive_from_variant.rs) pins that `#[derive(CgpVariant)]` refuses a non-enum item at parse time, and that the shared single-unnamed-field requirement rejects malformed variants. - -## Source - -- Entry point: `derive_cgp_variant` in [cgp-macro-lib/src/cgp_variant.rs](../../../crates/macros/cgp-macro-lib/src/cgp_variant.rs). -- Codegen: `ItemCgpVariant::to_items` in [cgp-macro-core/src/types/cgp_data/variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs), which composes `derive_has_fields_impls_from_enum`, `derive_from_variant_from_enum`, and the [derive_extractor/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/) helpers; the AST types are documented in [asts/cgp_data.md](../asts/cgp_data.md). -- The runtime traits live in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/). diff --git a/docs/implementation/entrypoints/derive_extract_field.md b/docs/implementation/entrypoints/derive_extract_field.md deleted file mode 100644 index a9d17c66..00000000 --- a/docs/implementation/entrypoints/derive_extract_field.md +++ /dev/null @@ -1,74 +0,0 @@ -# `#[derive(ExtractField)]` — implementation - -`#[derive(ExtractField)]` emits just the incremental-extractor slice of the variant machinery: the owned and borrowed `__Partial{Name}` companion enums plus the `HasExtractor`/`HasExtractorRef`/`HasExtractorMut`, `PartialData`, `FinalizeExtract`, and per-variant `ExtractField` impls that peel an enum apart one variant at a time. This document covers how that codegen works; for the accepted syntax and the full expansion, read the reference document [reference/derives/derive_extract_field.md](../../reference/derives/derive_extract_field.md). - -## Entry point - -The macro is driven by the `derive_extract_field` function in [cgp-macro-lib/src/derive_extract_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_extract_field.rs). It parses the input into a `syn::ItemEnum`, wraps it in an `ItemCgpVariant`, and calls `to_extract_field_items` — the same method the enum path of `#[derive(CgpData)]` uses for its extractor slice: - -```rust -let variant = ItemCgpVariant { item_enum }; -let items = variant.to_extract_field_items()?; -``` - -Applying the derive to a non-enum item fails at `syn::parse2`. - -## Pipeline - -There is no multi-stage transform. `ItemCgpVariant::to_extract_field_items` names two companion enums — `__Partial{ContextName}` (owned) and `__PartialRef{ContextName}` (borrowed) — and composes the helpers in the [`derive_extractor/`](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/) submodule. Most helpers run twice, once for each companion enum, selected by a `bool` that adds the borrowing `'__a`/`MapTypeRef` generics for the ref form. The [`cgp_data` AST stack](../asts/cgp_data.md) documents `ItemCgpVariant` and the field-tag types. - -## Generated items - -The derive centers on two partial companion enums. `__Partial{Name}` is a clone of the input enum that gains one `MapType` parameter per variant and wraps each payload in that parameter's `Map`; `IsPresent` keeps the payload and `IsVoid` maps it to the empty `Void`, so a variant's remaining-possibility is encoded in the type. `__PartialRef{Name}` adds a `'__a__` lifetime and a `MapTypeRef` parameter that selects a shared or mutable borrow of each payload: - -```rust -pub enum __PartialShape<__F0__: MapType, __F1__: MapType> { - Circle(<__F0__ as MapType>::Map), - Rectangle(<__F1__ as MapType>::Map), -} -``` - -Around the enums the derive emits: `PartialData` for each (both target the original enum); `HasExtractor` (owned, all variants `IsPresent`) with `to_extractor`/`from_extractor` that map each concrete variant across, plus `HasExtractorRef` (over `__PartialRef…<'__a__, IsRef, …>`) and `HasExtractorMut` (over `IsMut`); a `FinalizeExtract` impl for the all-`IsVoid` configuration of each enum, whose body is `match self {}` because that configuration is uninhabited; and, per variant, an `ExtractField` impl in scope only when that variant's marker is `IsPresent`. The extract impl returns `Ok(value)` on a match and `Err(remainder)` on a miss, where the remainder flips that variant's marker to `IsVoid`: - -```rust -impl<__F1__: MapType> ExtractField for __PartialShape { - type Value = Circle; - type Remainder = __PartialShape; - fn extract_field(self, _: PhantomData) -> Result { /* … */ } -} -``` - -Each failed extraction narrows the remainder by one `IsVoid`, so a chain of `extract_field` calls becomes a provably exhaustive match: once every marker is `IsVoid`, the value inhabits `FinalizeExtract` and can be discharged without a wildcard. The `FinalizeExtract` and `FinalizeExtractResult` traits are defined in the field crate; the derive supplies only the all-void impl. - -## Behavior and corner cases - -A variant's name is keyed by the [`Symbol!`](../../reference/macros/symbol.md) of its identifier, and the enum's generic parameters are threaded onto every impl and onto both companion enums, with the ref enum additionally bounding the type parameters by its `'__a__` lifetime. The reserved `'__a__` name (rather than a bare `'a`) is what lets the derive apply to an enum whose own lifetime parameter is named `'a` without the two colliding. The `HasExtractorRef`/`HasExtractorMut` associated types carry the `where Self: '__a__` bound that a borrowed extractor needs. - -A variantless enum is special-cased so its degenerate expansion still compiles. Because such an enum is uninhabited and borrows nothing, the borrowed partial enum `__PartialRef{Name}` is emitted as a bare empty enum with neither the `'__a__` lifetime nor the `__R__: MapTypeRef` selector — leaving them in would make both unused parameters (`E0392`) — and every borrowed accessor (`extractor_ref`/`extractor_mut`, and the sibling `HasFields` `to_fields_ref`) matches the dereferenced place with `match *self {}` rather than `match self {}`, since a bare match over `&Self` is non-exhaustive when `Self` is uninhabited (a reference is always considered inhabited, `E0004`). The owned side needs no special case: `to_extractor`, `from_extractor`, and the owned `FinalizeExtract` all match owned uninhabited values directly. The `HasExtractorRef`/`HasExtractorMut` GATs keep their `'__a__` parameter because the trait declares it, but the empty partial enum on the right-hand side takes no arguments. - -This derive emits no `HasFields` representation impls and no `FromVariant` constructors — those come from [`#[derive(HasFields)]`](derive_has_fields.md) and [`#[derive(FromVariant)]`](derive_from_variant.md); `ExtractField` is purely the deconstruction slice, included wholesale by [`#[derive(CgpVariant)]`](derive_cgp_variant.md) and [`#[derive(CgpData)]`](derive_cgp_data.md). - -## Error spans - -Each generated impl is re-spanned onto the token it derives from, so a compiler error points at that token rather than at the whole `#[derive(ExtractField)]`. The per-variant `ExtractField` impls are aimed at the variant they match, and the whole-enum `HasExtractor`/`HasExtractorRef`/`HasExtractorMut`/`FinalizeExtract`/`PartialData` impls at the enum name. Each goes through [`override_item_span`](../README.md#spans-aim-generated-items-at-the-token-the-user-wrote), moving only the `impl`/`{ … }` boundary — the mechanism the [`#[derive(HasField)]`](derive_has_field.md#error-spans) doc explains in full. The `__Partial{Name}`/`__PartialRef{Name}` companion enums are cloned from the user's own enum, so their tokens already carry meaningful spans and need no re-spanning. - -## Known issues - -The extractor codegen requires every variant to be a single-unnamed-field tuple variant (enforced by `get_variant_type` in the `derive_extractor/utils.rs` helper). A fieldless variant like `Empty`, a multi-field variant like `Pair(A, B)`, or a struct-style variant like `Named { x: A }` makes the macro fail with "Expected variant to contain exactly one unnamed field." There is no per-variant opt-out, so an enum mixing variant shapes cannot derive the extractor at all; the same requirement applies to [`#[derive(FromVariant)]`](derive_from_variant.md) and therefore to `#[derive(CgpVariant)]`/`#[derive(CgpData)]` on such an enum. The reference document records the user-visible form of this limitation in its own Known issues. - -## Tests - -`#[derive(ExtractField)]` has no snapshot macro of its own; the extractor items it emits are part of the variant expansion pinned by the `snapshot_derive_cgp_data!` snapshots indexed in [derive_cgp_data.md's Snapshots section](derive_cgp_data.md#snapshots). The behavioral extractor tests in [crates/tests/cgp-tests/tests/extensible_variants/](../../../crates/tests/cgp-tests/tests/extensible_variants/) exercise the machinery: - -- [shape_dispatch.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch.rs) drives the owned extractor. -- [shape_dispatch_ref.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/shape_dispatch_ref.rs) drives the borrowed extractor. -- [variant_dispatch.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/variant_dispatch.rs) drives the extract-and-dispatch flow. -- [derive_cgp_data_empty.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_empty.rs) snapshots the variantless-enum expansion, pinning the empty-enum special case: bare `__PartialNever`/`__PartialRefNever` enums with no parameters and `match *self {}` in the borrowed accessors. -- The single-unnamed-field requirement (Known issues) has no dedicated failure case in `cgp-macro-tests`, but is covered end-to-end through the variant derives by [parser_rejections/derive_from_variant.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/derive_from_variant.rs). -- The reserved-`'__a__` lifetime is exercised against a lifetime-parameterized enum by [derive_cgp_data_lifetime.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/derive_cgp_data_lifetime.rs), which derives `#[derive(CgpData)]` on an `enum Message<'a>` and drives the owned, borrowed, and mutable extractors. - -## Source - -- Entry point: `derive_extract_field` in [cgp-macro-lib/src/derive_extract_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_extract_field.rs). -- Codegen: `ItemCgpVariant::to_extract_field_items` in [cgp-macro-core/src/types/cgp_data/variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs), which composes the [derive_extractor/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/) helpers (`extractor_enum.rs`, `partial_data.rs`, `has_extractor_impl.rs`, `finalize_extract_impl.rs`, `extract_field_impls.rs`, `utils.rs`); the AST types are documented in [asts/cgp_data.md](../asts/cgp_data.md). -- The `ExtractField`, `HasExtractor`/`HasExtractorRef`/`HasExtractorMut`, `FinalizeExtract`, `FinalizeExtractResult`, and `PartialData` traits and the `MapType`/`MapTypeRef` markers live under [crates/core/cgp-field/src/](../../../crates/core/cgp-field/src/). diff --git a/docs/implementation/entrypoints/derive_from_variant.md b/docs/implementation/entrypoints/derive_from_variant.md deleted file mode 100644 index 379311e0..00000000 --- a/docs/implementation/entrypoints/derive_from_variant.md +++ /dev/null @@ -1,56 +0,0 @@ -# `#[derive(FromVariant)]` — implementation - -`#[derive(FromVariant)]` emits just the variant-construction slice of the variant machinery: one `FromVariant` impl per variant, so an enum can be built generically from any single variant addressed by a type-level tag. This document covers how that codegen works; for the accepted syntax and the full expansion, read the reference document [reference/derives/derive_from_variant.md](../../reference/derives/derive_from_variant.md). - -## Entry point - -The macro is driven by the `derive_from_variant` function in [cgp-macro-lib/src/derive_from_variant.rs](../../../crates/macros/cgp-macro-lib/src/derive_from_variant.rs). It parses the input into a `syn::ItemEnum`, wraps it in an `ItemCgpVariant`, and calls `to_from_variant_impls` — the same method the enum path of `#[derive(CgpData)]` uses for its constructor slice: - -```rust -let variant = ItemCgpVariant { item_enum }; -let item_impls = variant.to_from_variant_impls()?; -``` - -Applying the derive to a non-enum item fails at `syn::parse2`. - -## Pipeline - -There is no multi-stage transform. `ItemCgpVariant::to_from_variant_impls` forwards to the single codegen helper `derive_from_variant_from_enum`, which walks the enum's variants and emits one constructor impl each. The [`cgp_data` AST stack](../asts/cgp_data.md) documents `ItemCgpVariant` and the `Symbol` field-tag type. - -## Generated items - -The derive emits one [`FromVariant`](../../reference/traits/from_variant.md) impl per variant and nothing else — no companion type and no presence tracking, making it the simplest derive in the family. Each impl is keyed by the [`Symbol!`](../../reference/macros/symbol.md) of the variant's name, takes the variant's payload as the associated `Value`, and wraps it in the variant: - -```rust -impl FromVariant for Shape { - type Value = Circle; - fn from_variant(_tag: PhantomData, value: Self::Value) -> Self { Self::Circle(value) } -} -``` - -The `PhantomData` argument exists only to let a caller select which variant to build when several `FromVariant` impls are in scope. The `FromVariant` trait itself is defined in the field crate; the derive supplies only the per-variant impls. - -## Behavior and corner cases - -The enum's generic parameters are threaded onto every impl. This derive emits no `HasFields` representation impls and no extractor — those come from [`#[derive(HasFields)]`](derive_has_fields.md) and [`#[derive(ExtractField)]`](derive_extract_field.md); `FromVariant` is purely the construction slice, included wholesale by [`#[derive(CgpVariant)]`](derive_cgp_variant.md) and [`#[derive(CgpData)]`](derive_cgp_data.md). - -## Error spans - -Each per-variant impl is re-spanned onto the variant it constructs, so a coherence conflict (`E0119`) with a hand-written `FromVariant` impl lands its caret on the variant the user wrote rather than on the whole `#[derive(FromVariant)]`. `derive_from_variant_from_enum` passes each finished impl through [`override_item_span`](../README.md#spans-aim-generated-items-at-the-token-the-user-wrote), moving only the `impl`/`{ … }` boundary — the mechanism the [`#[derive(HasField)]`](derive_has_field.md#error-spans) doc explains in full. - -## Known issues - -Like the extractor derive, `#[derive(FromVariant)]` requires every variant to be a single-unnamed-field tuple variant. A fieldless, multi-field, or struct-style variant makes the macro fail with "Expected variant to contain exactly one unnamed field," with no per-variant opt-out. The requirement is described alongside the extractor's in [`derive_extract_field`](derive_extract_field.md#known-issues), and the reference document records its user-visible form. - -## Tests - -`#[derive(FromVariant)]` has no snapshot macro of its own; the constructor impls it emits are part of the variant expansion pinned by the `snapshot_derive_cgp_data!` snapshots indexed in [derive_cgp_data.md's Snapshots section](derive_cgp_data.md#snapshots). - -- The behavioral variant tests in [crates/tests/cgp-tests/tests/extensible_variants/](../../../crates/tests/cgp-tests/tests/extensible_variants/) — notably [variant_dispatch.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/variant_dispatch.rs) — construct enums through the generated `from_variant`. -- [parser_rejections/derive_from_variant.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/derive_from_variant.rs) pins the single-unnamed-field requirement: the derive rejects a fieldless, a multi-field, and a struct-style variant. - -## Source - -- Entry point: `derive_from_variant` in [cgp-macro-lib/src/derive_from_variant.rs](../../../crates/macros/cgp-macro-lib/src/derive_from_variant.rs). -- Codegen: `ItemCgpVariant::to_from_variant_impls` in [cgp-macro-core/src/types/cgp_data/variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs), which delegates to `derive_from_variant_from_enum` in [cgp-macro-core/src/types/cgp_data/derive_from_variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs); the AST types are documented in [asts/cgp_data.md](../asts/cgp_data.md). -- The `FromVariant` trait is defined in [crates/core/cgp-field/src/traits/from_variant.rs](../../../crates/core/cgp-field/src/traits/from_variant.rs). diff --git a/docs/implementation/entrypoints/derive_has_field.md b/docs/implementation/entrypoints/derive_has_field.md deleted file mode 100644 index db947db2..00000000 --- a/docs/implementation/entrypoints/derive_has_field.md +++ /dev/null @@ -1,83 +0,0 @@ -# `#[derive(HasField)]` — implementation - -`#[derive(HasField)]` gives a struct tag-keyed field access by emitting one `HasField` impl and one `HasFieldMut` impl per field, each keyed by the field's type-level name. This document covers how that codegen works; for the accepted syntax and the full expansion a user sees, read the reference document [reference/derives/derive_has_field.md](../../reference/derives/derive_has_field.md). - -## Entry point - -The macro is driven by the thin `derive_has_field` function in [cgp-macro-lib/src/derive_has_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_has_field.rs). It parses the input into a `syn::ItemStruct`, wraps it in an `ItemCgpRecord`, and calls `to_has_field_impls`, which returns the getter impls to emit: - -```rust -let record = ItemCgpRecord { item_struct }; -let item_impls = record.to_has_field_impls()?; -``` - -Because it parses straight into `ItemStruct`, applying the derive to a non-struct item fails at `syn::parse2`. All codegen lives in `cgp-macro-core`; the entry function only concatenates the returned impls. - -## Pipeline - -There is no multi-stage transform. `ItemCgpRecord::to_has_field_impls` forwards to the single codegen helper `derive_has_field_impls_from_struct`, which walks the struct's fields and emits the getter impls. The [`cgp_data` AST stack](../asts/cgp_data.md) documents `ItemCgpRecord` and the `Symbol`/`Index` field-tag types the helper uses. - -## Generated items - -The derive emits two impls per field — a `HasField` read accessor and a `HasFieldMut` mutable accessor — and leaves the struct definition untouched. A named field is keyed by the [`Symbol!`](../../reference/macros/symbol.md) of its identifier; a tuple field is keyed by its positional [`Index`](../../reference/types/index.md). The field's declared type becomes the associated `Value`, and the body simply borrows the corresponding field: - -```rust -// named field -impl HasField for Person { - type Value = String; - fn get_field(&self, key: PhantomData) -> &Self::Value { &self.name } -} -// tuple field — same shape, Index tag, &self.0 body -impl HasField> for Rectangle { - type Value = f64; - fn get_field(&self, key: PhantomData>) -> &Self::Value { &self.0 } -} -``` - -The `HasFieldMut` impl for each field mirrors the read impl, returning `&mut Self::Value` from `&mut self.`. Whether a field's tag is a `Symbol` or an `Index` is decided by mapping the field's `syn::Member` to a [`FieldName`](../asts/cgp_data.md#symbol-index-and-fieldname), whose `ToTokens` picks the right type-level spelling. - -## Behavior and corner cases - -The struct's **generic parameters** are threaded onto every impl: the helper splits the generics into impl-generics, type-generics, and a `where` clause, so `struct Wrapper { value: T }` yields `impl HasField for Wrapper` with `Value = T`, and a lifetime field carries the struct's lifetime through (`impl<'a> HasField<…> for Context<'a>` with `Value = &'a str`). - -A **unit struct** has no fields, so the derive emits nothing rather than erroring. The helper only handles the named-field and tuple-field cases; there is no whole-struct output here — the aggregate `HasFields` view comes from [`#[derive(HasFields)]`](derive_has_fields.md) instead. - -A **raw-identifier field** such as `r#type` is tagged by its logical name: `Symbol::from_ident` calls `Ident::unraw`, so the field expands to `HasField`, not `HasField`. Without this, the tag would encode the literal string `r#type` (a length-6 symbol whose second character is `#`) and no `Symbol!("type")` bound could match it. The accessor body still borrows through the raw identifier (`&self.r#type`), since that is the field's real name. This is exercised by the [field_access/raw_ident.rs](../../../crates/tests/cgp-tests/tests/field_access/raw_ident.rs) snapshot. - -Field access through **smart pointers** is not the derive's doing: `HasField`/`HasFieldMut` have blanket impls over `Deref`/`DerefMut` targets defined in the field crate, so a `Box` resolves through to the inner struct without the derive generating anything for the pointer type. - -## Error spans - -Each generated impl is re-spanned onto the field it derives from, so a compiler error about one field's `HasField`/`HasFieldMut` impl points at that field rather than at the whole `#[derive(HasField)]`. The impls are built with `parse_internal!`, whose tokens all carry the macro's `call_site` span — the entire derived struct — so without a re-span every such error would underline the derive and say nothing about which field is involved. `derive_has_field_impls_from_struct` therefore passes each finished impl through [`override_item_span`](../README.md#spans-aim-generated-items-at-the-token-the-user-wrote), moving only its boundary tokens (the `impl` keyword and the `{ … }` body) onto the field's own span — the field identifier for a named field, the whole `syn::Field` for a tuple field, which has no identifier. This is the same technique the [`delegate_components!`](delegate_components.md#error-spans) impls use for their per-entry keys. - -Two diagnostics show the difference. A **coherence conflict** (`E0119`) — a hand-written `HasField` impl clashing with the one the derive emits — now lands its "conflicting implementation" caret on the `name` field instead of on the derive. More common in practice is the **near-impl hint** inside a missing-field check error: when a provider needs a field the struct lacks, `rustc` reports the unmet `HasField` bound and adds "but trait `HasField<…>` is implemented for it," pointing at the *nearest existing* field impl. That caret now lands on the field whose impl is cited, so a struct that derives `HasField` for several fields shows each near-miss on its own field rather than collapsing them all onto the derive attribute — where the encoded `Symbol` tags were the reader's only way to tell them apart. See the [check-trait-failure](../../errors/checks/check-trait-failure.md) class for the full diagnostic. - -Only the boundary tokens move; every interior token — the `HasField` reference, the `Symbol!`/`Index` tag, the `Value` type — keeps its own span. That is what keeps the field navigable in an editor: rust-analyzer maps a source token to its expansion by source range, so re-spanning a resolvable reference onto the field would hijack go-to-definition on the field, whereas a keyword and a delimiter cannot. The caret half is pinned by the raw `.rust.stderr` baselines of the `cargo-cgp` UI fixtures that exercise a derived context — [`fields/base_area_1`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/base_area_1.rs) and the multi-field [`duplication/density_3`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/duplication/density_3.rs) among them — so a regression that drops the re-span changes those snapshots. - -## Snapshots - -Every `snapshot_derive_has_field!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint. They live in the `field_access` target, which owns the `#[derive(HasField)]` expansion: - -- [field_access/index.rs](../../../crates/tests/cgp-tests/tests/field_access/index.rs) — a tuple struct, each field keyed by `Index<0>`/`Index<1>` rather than a `Symbol!`. -- [field_access/lifetime_field.rs](../../../crates/tests/cgp-tests/tests/field_access/lifetime_field.rs) — a struct lifetime lifted onto the impls, with a borrowed field type (`&'a str`) kept as `Value`. -- [field_access/chain.rs](../../../crates/tests/cgp-tests/tests/field_access/chain.rs) — the canonical named-field expansion, over two owned structs. -- [field_access/raw_ident.rs](../../../crates/tests/cgp-tests/tests/field_access/raw_ident.rs) — a raw-identifier field (`r#type`), pinning that the tag is the unrawed `Symbol!("type")` while the body borrows `&self.r#type`. -- [field_access/chain_inner_life.rs](../../../crates/tests/cgp-tests/tests/field_access/chain_inner_life.rs) — the inner struct carries a lifetime, threaded onto its impls. -- [field_access/chain_outer_life.rs](../../../crates/tests/cgp-tests/tests/field_access/chain_outer_life.rs) — the outer struct borrows the inner one, with the outer lifetime on its impls. -- [field_access/chain_deeply_nested.rs](../../../crates/tests/cgp-tests/tests/field_access/chain_deeply_nested.rs) — five structs each deriving `HasField`, pinning the plain expansion repeated across a deep chain. - -## Tests - -The behavioral tests confirm the generated getters read the right fields: - -- [field_access/index.rs](../../../crates/tests/cgp-tests/tests/field_access/index.rs) reads a tuple struct's fields at run time through `get_field` with `Index<0>`/`Index<1>` tags. -- [field_access/lifetime_field.rs](../../../crates/tests/cgp-tests/tests/field_access/lifetime_field.rs) reads a lifetime-carrying field back out. -- [field_access/chain.rs](../../../crates/tests/cgp-tests/tests/field_access/chain.rs), [chain_inner_life.rs](../../../crates/tests/cgp-tests/tests/field_access/chain_inner_life.rs), [chain_outer_life.rs](../../../crates/tests/cgp-tests/tests/field_access/chain_outer_life.rs), and [chain_deeply_nested.rs](../../../crates/tests/cgp-tests/tests/field_access/chain_deeply_nested.rs) compose the generated getters through `ChainGetters` to read a nested field in one hop. -- [field_access/symbol.rs](../../../crates/tests/cgp-tests/tests/field_access/symbol.rs) and [field_access/index_display.rs](../../../crates/tests/cgp-tests/tests/field_access/index_display.rs) exercise the `Symbol!`/`Index` tag types the derive emits. -- [field_access/raw_ident.rs](../../../crates/tests/cgp-tests/tests/field_access/raw_ident.rs) reads a raw-identifier field back through `get_field` with the unrawed `Symbol!("type")` tag, confirming the `r#` prefix is dropped from the tag. - -## Source - -- Entry point: `derive_has_field` in [cgp-macro-lib/src/derive_has_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_has_field.rs). -- It calls `ItemCgpRecord::to_has_field_impls` in [cgp-macro-core/src/types/cgp_data/record.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/record.rs), whose codegen is `derive_has_field_impls_from_struct` in [cgp-macro-core/src/types/cgp_data/derive_has_field.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_field.rs); the AST types are documented in [asts/cgp_data.md](../asts/cgp_data.md). -- The `HasField`/`HasFieldMut` traits are defined in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/). diff --git a/docs/implementation/entrypoints/derive_has_fields.md b/docs/implementation/entrypoints/derive_has_fields.md deleted file mode 100644 index 9f50752d..00000000 --- a/docs/implementation/entrypoints/derive_has_fields.md +++ /dev/null @@ -1,79 +0,0 @@ -# `#[derive(HasFields)]` — implementation - -`#[derive(HasFields)]` gives a struct or enum a whole-shape view by emitting the representation impls — `HasFields`, `HasFieldsRef`, `FromFields`, `ToFields`, `ToFieldsRef` — that describe the type as a single type-level product (for a struct) or sum (for an enum). This document covers how that codegen works; for the accepted syntax and the full expansion, read the reference document [reference/derives/derive_has_fields.md](../../reference/derives/derive_has_fields.md). - -## Entry point - -The macro is driven by the `derive_has_fields` function in [cgp-macro-lib/src/derive_has_fields.rs](../../../crates/macros/cgp-macro-lib/src/derive_has_fields.rs). Unlike the other data derives it dispatches on shape at the entry point: it parses the input as a `syn::Item` and branches on `struct` versus `enum`, rejecting anything else with a spanned "expect body to be either a struct or enum" error: - -```rust -let impls = match item { - Item::Struct(item_struct) => ItemCgpRecord { item_struct }.to_has_fields_impls()?, - Item::Enum(item_enum) => derive_has_fields_impls_from_enum(&item_enum)?, - _ => return Err(/* struct or enum */), -}; -``` - -The struct path goes through `ItemCgpRecord`; the enum path calls the enum codegen helper directly. - -## Pipeline - -There is no multi-stage transform. Both paths call a single codegen helper — `derive_has_fields_impls_from_struct` for a struct, `derive_has_fields_impls_from_enum` for an enum — that emits the five representation impls. The [`cgp_data` AST stack](../asts/cgp_data.md) documents `ItemCgpRecord` and the `Symbol`/`Index` field-tag types. - -## Generated items - -The derive emits five impls and leaves the type definition untouched. The load-bearing part is the `Fields` associated type: a struct's fields become a [`Product`](../../reference/macros/product.md) of [`Field`](../../reference/types/field.md) entries over the `Cons`/`Nil` spine, and an enum's variants become a [`Sum`](../../reference/macros/sum.md) of `Field` entries over the `Either`/`Void` spine. Named fields and variant names are keyed by [`Symbol!`](../../reference/macros/symbol.md); tuple fields by [`Index`](../../reference/types/index.md). - -```rust -// struct → product -impl HasFields for Person { - type Fields = Cons, Cons, Nil>>; -} -// enum → sum, terminated by Void -impl HasFields for Shape { - type Fields = Either, Either, Void>>; -} -``` - -Alongside the shape type, the derive emits `HasFieldsRef` (the same product/sum with each value borrowed under a fresh `'__a` lifetime) and the three conversions: `ToFields` builds the product/sum from a value, `FromFields` destructures it back, and `ToFieldsRef` builds the borrowed form. For a struct the conversions read `self..into()` into a `Cons(…)` chain and pattern-match `Cons(…)` back into `Self { … }`; for an enum they match each concrete variant to its `Either` arm and back. The product spine is built by `item_fields_to_product_type` in the `product.rs` submodule and the sum spine by `variants_to_sum_type` in `sum.rs`; the entries are chained right-associatively. - -## Behavior and corner cases - -A **single-field tuple struct** (a newtype) is special-cased: its `Fields` is the inner type directly, not a one-element `Cons, _>, Nil>`, and the conversions pass the single value straight through. A tuple struct with more than one field is not special-cased — its fields are keyed by `Index` and chained into the usual product. - -A **unit struct** produces `Nil` as its `Fields`, and its conversions round-trip through the empty product. - -The type's **generic parameters and `where` clause** are threaded onto all five impls. A borrowed field type appears verbatim in the product, and `HasFieldsRef` layers its own `'__a` borrow on top — so a field of type `&'a Name` becomes `&'__a &'a Name` in `FieldsRef<'__a>`. The `HasFieldsRef` associated type carries the `where Self: '__a` bound that every borrowed representation needs. - -An **enum** always maps each variant's payload into a `Field` entry regardless of the payload's own shape; the `HasFields` enum path does not impose the single-unnamed-field requirement that the extractor and `FromVariant` derives do, because it only names the payload type rather than deconstructing it. - -## Error spans - -All five impls are keyed on the whole type, so each is re-spanned onto the struct or enum name the user wrote rather than left at the derive's `call_site` span. A coherence conflict (`E0119`) — a hand-written `HasFields` impl clashing with the derived one — therefore lands its caret on the type name instead of on the whole `#[derive(HasFields)]`. `derive_has_fields_impls_from_struct` and `derive_has_fields_impls_from_enum` pass each finished impl through [`override_item_span`](../README.md#spans-aim-generated-items-at-the-token-the-user-wrote), which moves only the `impl`/`{ … }` boundary — the mechanism the [`#[derive(HasField)]`](derive_has_field.md#error-spans) doc explains in full. - -## Snapshots - -Every `snapshot_derive_has_fields!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint. The struct expansion is owned by the `extensible_records` target and the enum expansion by `extensible_variants`: - -- [extensible_records/struct_single_named_field.rs](../../../crates/tests/cgp-tests/tests/extensible_records/struct_single_named_field.rs) — the canonical struct expansion, one named field. -- [extensible_records/struct_two_named_fields.rs](../../../crates/tests/cgp-tests/tests/extensible_records/struct_two_named_fields.rs) — a two-field `Cons<_, Cons<_, Nil>>` product. -- [extensible_records/struct_single_unnamed_field.rs](../../../crates/tests/cgp-tests/tests/extensible_records/struct_single_unnamed_field.rs) — the newtype special case, `Fields` = the inner type. -- [extensible_records/struct_tuple_fields.rs](../../../crates/tests/cgp-tests/tests/extensible_records/struct_tuple_fields.rs) — a multi-field tuple struct keyed by `Index`. -- [extensible_records/struct_generic.rs](../../../crates/tests/cgp-tests/tests/extensible_records/struct_generic.rs) — a generic struct with a `where` clause threaded onto each impl. -- [extensible_records/struct_generic_lifetime.rs](../../../crates/tests/cgp-tests/tests/extensible_records/struct_generic_lifetime.rs) — a lifetime plus type parameter, with the layered `&'__a &'a` borrow in `FieldsRef`. -- [extensible_variants/has_fields_enum.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum.rs) — the canonical enum expansion, a `Sum!` of `Field`. -- [extensible_variants/has_fields_enum_generic.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum_generic.rs) — a generic enum with a lifetime and a reference-typed payload. - -## Tests - -The snapshot tests above double as the coverage: - -- Each pins one field-shape and, where paired with runtime assertions, round-trips a value through `to_fields`/`from_fields`. -- The `struct_single_unnamed_field` snapshot is the guard on the newtype special case. -- `struct_generic`/`struct_generic_lifetime` and `has_fields_enum_generic` guard the generic-threading behavior. - -## Source - -- Entry point: `derive_has_fields` in [cgp-macro-lib/src/derive_has_fields.rs](../../../crates/macros/cgp-macro-lib/src/derive_has_fields.rs). -- The struct path calls `ItemCgpRecord::to_has_fields_impls` in [cgp-macro-core/src/types/cgp_data/record.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/record.rs); both paths land in the [derive_has_fields/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/) submodule, where `derive_struct.rs`/`derive_enum.rs` drive the five impls, `product.rs` (`item_fields_to_product_type`) builds the struct product, `sum.rs` (`variants_to_sum_type`) builds the enum sum, and the `from_fields_*`/`to_fields_*`/`to_fields_ref_*` files build the conversions. The AST types are documented in [asts/cgp_data.md](../asts/cgp_data.md). -- The `HasFields`, `HasFieldsRef`, `FromFields`, `ToFields`, and `ToFieldsRef` traits and the `Field`/`Either`/`Void` building blocks live under [crates/core/cgp-field/src/](../../../crates/core/cgp-field/src/). diff --git a/docs/implementation/entrypoints/path.md b/docs/implementation/entrypoints/path.md deleted file mode 100644 index 3d9504c2..00000000 --- a/docs/implementation/entrypoints/path.md +++ /dev/null @@ -1,52 +0,0 @@ -# `Path!` — implementation - -`Path!` is a function-like macro that expands an `@`-prefixed dotted name into a `PathCons` chain — the type-level route CGP's namespace and `RedirectLookup` machinery walks through nested delegation tables. This document covers how the macro parses the segments and emits the chain; for the accepted syntax and the full expansion a user sees, read the reference document [reference/macros/path.md](../../reference/macros/path.md). - -## Entry point - -The macro is driven by the thin `Path` function in [cgp-macro-lib/src/path.rs](../../../crates/macros/cgp-macro-lib/src/path.rs), which parses the body into a `UniPath` and emits its tokens directly: - -```rust -let unipath: UniPath = parse2(body)?; -Ok(unipath.to_token_stream()) -``` - -There is no `eval` step — the `UniPath` type both parses the `@`-path and, through its `ToTokens`, folds the parsed segments into the `PathCons` chain. The `UniPath` type and the segment types around it are documented in the [`path` AST stack](../asts/path.md). - -## Pipeline - -The macro is a single parse-then-emit step. `UniPath::parse` consumes the leading `@` and a dot-separated, non-empty run of segments, classifying each into a `PathElement` (a `Symbol` or a named `Type`); `UniPath::to_tokens` right-folds those segments into the `PathCons` chain. The classification is where the interesting decision lives, and it belongs to `PathElement` in the [`path` AST document](../asts/path.md). - -## Generated items - -`Path!` emits a single type: a right-nested chain of `PathCons` terminated by `Nil`, one `PathCons` per segment. Each lowercase, non-primitive segment is encoded as a `Symbol!` type-level string and every other segment is kept as the named type it spells: - -```rust -// Path!(@app.error.ErrorRaiserComponent) -PathCons< - Symbol!("app"), - PathCons>, -> -``` - -A lowercase segment expands further, since `Symbol!("app")` is itself a `Symbol<3, Chars<'a', …>>` chain. A single-segment path becomes `PathCons`. `PathCons` and `Nil` are emitted through the [export markers](../../../crates/macros/cgp-macro-core/src/exports.rs). - -## Behavior and corner cases - -The leading `@` is required — `UniPath::parse` consumes an `At` token before anything else, so a body without it fails at parse time — and at least one segment must follow, because the segments are read with `parse_separated_nonempty`. - -Each segment is first parsed as a full Rust `Type`, then reclassified: only a segment that reduces to a single identifier beginning with a lowercase ASCII letter, and is not a primitive type name, becomes a `Symbol`; everything else stays the named type. The primitive exception means a lowercase name like `u32`, `bool`, `usize`, or `str` is kept as the primitive type rather than turned into a symbol. This is the same convention `#[cgp_namespace]` entries and `#[prefix(...)]` attributes embed, and those constructs reuse the same segment and fold machinery rather than the `Path!` entry function. - -## Tests - -`Path!` has no snapshot macro of its own, and it is rarely written directly — the `@`-path syntax is almost always embedded inside `#[cgp_namespace]` entries and `#[prefix(...)]` attributes instead of the bare macro. Its expansion is therefore exercised indirectly through the namespace and prefix machinery that shares its parsing. - -- [namespaces/redirect_lookup.rs](../../../crates/tests/cgp-tests/tests/namespaces/redirect_lookup.rs) pins, through a `snapshot_cgp_component!` golden, how a `#[prefix(@bar.baz in DefaultNamespace)]` attribute lowers a component lookup into a `RedirectLookup` over the `PathCons` chain the same segment fold produces. -- [namespaces/namespace_symbol_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_symbol_path.rs) and [namespaces/namespace_type_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_type_path.rs) exercise the lowercase-symbol and capitalized-type segment classifications through `#[cgp_namespace]` `@`-paths. - -## Source - -- Entry point: `Path` in [cgp-macro-lib/src/path.rs](../../../crates/macros/cgp-macro-lib/src/path.rs). -- The `UniPath`, `PathElement`, and the wider path stack: [cgp-macro-core/src/types/path/](../../../crates/macros/cgp-macro-core/src/types/path/), documented in [asts/path.md](../asts/path.md). -- Runtime spine `PathCons`: defined in [cgp-base-types](../../../crates/core/cgp-base-types/src/types/path.rs). -- The [`RedirectLookup`](../../reference/providers/redirect_lookup.md) provider that walks a path: [cgp-component](../../../crates/core/cgp-component/src/providers/redirect_lookup.rs). diff --git a/docs/implementation/entrypoints/product.md b/docs/implementation/entrypoints/product.md deleted file mode 100644 index 5c57b9e5..00000000 --- a/docs/implementation/entrypoints/product.md +++ /dev/null @@ -1,62 +0,0 @@ -# `Product!` and `product!` — implementation - -`Product!` and `product!` are function-like macros that expand a comma-separated list into a `Cons`/`Nil` chain — a type at the type level for `Product!`, a matching value for `product!`. This document covers how each parses its list and emits the chain; for the accepted syntax and the full expansion a user sees, read the reference document [reference/macros/product.md](../../reference/macros/product.md). - -## Entry point - -The two macros are driven by the `Product` and `product` functions in [cgp-macro-lib/src/product.rs](../../../crates/macros/cgp-macro-lib/src/product.rs), which follow the same shape: parse the body into an AST type, call its `eval`, and emit the result. - -```rust -// Product! -let product_type: ProductType = parse2(body)?; -Ok(product_type.eval()?.to_token_stream()) - -// product! -let product_expr: ProductExpr = parse2(body)?; -Ok(product_expr.eval()?.to_token_stream()) -``` - -Both parse the body as a comma-separated list, so a malformed element fails at `parse2`. The two AST types are documented together in the [`product` AST stack](../asts/product.md). - -## Pipeline - -Each macro is a parse-then-`eval` step with no further stages. `Product!`'s `Parse` impl reads a `Punctuated` and `product!`'s reads a `Punctuated`; each `eval` folds its list into the chain and re-parses the result through [`parse_internal!`](../macros/parse_internal.md), validating `Product!`'s output as a `syn::Type` and `product!`'s as a `syn::Expr`. The [`product` AST document](../asts/product.md) covers both types. - -## Generated items - -`Product!` emits a single type: a right-nested chain of `Cons` terminated by `Nil`, one `Cons` per element. - -```rust -// Product![A, B, C] -Cons>> -``` - -`product!` emits a value of that type using the `Cons` tuple-struct constructor rather than the type form, so the value's type is exactly what `Product!` builds over the corresponding element types: - -```rust -// product![a, b, c] -Cons(a, Cons(b, Cons(c, Nil))) -``` - -The chain is built by folding right-to-left onto `Nil`, so an empty `Product![]` (or `product![]`) is just `Nil`. `Cons` and `Nil` are emitted through the [export markers](../../../crates/macros/cgp-macro-core/src/exports.rs). - -## Behavior and corner cases - -The two macros parse at the level they emit at: `Product!` reads its elements as types and `product!` reads them as expressions, so a `product!` element may be any expression — a literal, a method call, an arithmetic expression — and not just a path that also parses as a type. Parsing and re-parsing at the right level is what keeps each macro in its position: `product!`'s `syn::Expr` output is valid in expression context and `Product!`'s `syn::Type` output in type context. A trailing comma is accepted on both because the list is parsed with `parse_terminated`, and an empty body is valid and yields `Nil` (a value for `product!`, a type for `Product!`). - -Because `eval` re-parses its output through `parse_internal!`, a fold that produced malformed tokens would surface as a spanned `syn::Error` rather than raw token garbage — though with `Cons`/`Nil` and well-formed elements this path does not normally fail. - -## Tests - -`Product!`/`product!` have no snapshot macro of their own; the type-level chain they build is exercised wherever a struct's field list is derived, since `#[derive(HasFields)]` emits a `Product!` of `Field` entries, and the value-level form has a dedicated behavioral test. - -- [extensible_records/product_value.rs](../../../crates/tests/cgp-tests/tests/extensible_records/product_value.rs) exercises the value-level `product!` directly: that expression items build the nested `Cons(..)`/`Nil` value, that its type is the matching `Product!`, and that the empty and trailing-comma forms work. -- [extensible_records/person_record.rs](../../../crates/tests/cgp-tests/tests/extensible_records/person_record.rs) and [extensible_records/record_derive.rs](../../../crates/tests/cgp-tests/tests/extensible_records/record_derive.rs) pin, through `snapshot_derive_cgp_data!` goldens, the `Product!` field spine a record derives, so the `Cons`/`Nil` shape is checked as embedded output. -- [handlers/pipe_handlers.rs](../../../crates/tests/cgp-tests/tests/handlers/pipe_handlers.rs) uses `Product![…]` to write a handler pipeline, exercising the type-level form as a list of provider types rather than fields. - -## Source - -- Entry points: `Product` and `product` in [cgp-macro-lib/src/product.rs](../../../crates/macros/cgp-macro-lib/src/product.rs). -- `ProductType` and `ProductExpr` AST types: [cgp-macro-core/src/types/product/](../../../crates/macros/cgp-macro-core/src/types/product/), documented in [asts/product.md](../asts/product.md). -- The fold re-parses through [parse_internal!](../macros/parse_internal.md). -- Runtime types `Cons` and `Nil`: defined in [cgp-base-types](../../../crates/core/cgp-base-types/src/types/). diff --git a/docs/implementation/entrypoints/snapshot_macros.md b/docs/implementation/entrypoints/snapshot_macros.md deleted file mode 100644 index 9ae4481a..00000000 --- a/docs/implementation/entrypoints/snapshot_macros.md +++ /dev/null @@ -1,81 +0,0 @@ -# The `snapshot_*!` macro family — implementation - -The `snapshot_*!` macros are the test-only proc macros that pin a CGP macro's expansion: each one invokes the real `cgp-macro-lib` entry function on an inline invocation, emits the generated code into the module *so it still compiles*, and generates a `#[test]` that asserts a pretty-printed `insta` snapshot of that same code. This document covers how the family is built; it has no reference document, because these macros are internal test utilities rather than user-facing CGP constructs. - -## Entry point - -The proc-macro shims live in [cgp-macro-test-util/src/lib.rs](../../../crates/macros/cgp-macro-test-util/src/lib.rs) — one `#[proc_macro]` per snapshot macro, each forwarding to a same-named function in [cgp-macro-test-util-lib](../../../crates/macros/cgp-macro-test-util-lib/) and converting a `syn::Error` into a compile error. The real logic is in `cgp-macro-test-util-lib`, split into the per-macro entrypoints under [src/entrypoints/](../../../crates/macros/cgp-macro-test-util-lib/src/entrypoints/) and the shared snapshot types and helpers. This crate is a peer of `cgp-macro-lib`, not part of `cgp-macro-core`; it depends on `cgp-macro-lib` so that a snapshot runs the *actual* macro rather than a reimplementation. - -The family members mirror the CGP macros they pin. The current set is `snapshot_cgp_component!`, `snapshot_cgp_impl!`, `snapshot_cgp_provider!`, `snapshot_cgp_new_provider!`, `snapshot_cgp_fn!`, `snapshot_cgp_getter!`, `snapshot_cgp_auto_getter!`, `snapshot_cgp_type!`, `snapshot_cgp_namespace!`, `snapshot_blanket_trait!`, `snapshot_delegate_components!`, `snapshot_check_components!`, `snapshot_delegate_and_check_components!`, `snapshot_derive_has_field!`, `snapshot_derive_has_fields!`, and `snapshot_derive_cgp_data!`. There is deliberately no snapshot macro for `#[cgp_computer]`, `#[cgp_producer]`, `#[cgp_auto_dispatch]`, or `#[async_trait]` — those extra-feature macros are pinned only behaviorally, through the handler, dispatch, and async tests indexed in their own implementation documents. - -## Pipeline - -Every family member follows the same three-step shape, differing only in which parser reads the invocation and which `cgp-macro-lib` function it calls: - -- **Parse** the macro body into a snapshot wrapper — a wrapper that captures the macro invocation to expand plus the trailing `#[test]` scaffold (the test name, the output binding, and the assertion expression). -- **Expand** by calling the corresponding `cgp-macro-lib` entry function (for example `cgp_macro_lib::cgp_component(attr, body)`) on the captured invocation, producing the same `TokenStream` a user would get. -- **Wrap** the expansion with `MacroSnapshot::wrap_output`, which pretty-prints it and stitches together the final output. - -Which parser is used depends on how the pinned macro is invoked, and the three shapes correspond to three wrapper types in [src/types/](../../../crates/macros/cgp-macro-test-util-lib/src/types/): - -- **`AttributeMacroSnapshot`** — for an attribute macro. It parses a `#[keyword(...)]` or `#[keyword { ... }]` attribute (its argument tokens become `attr`) followed by the annotated item (`ItemTrait`, `ItemImpl`, …). Used by `snapshot_cgp_component!`, `snapshot_cgp_impl!`, and the other attribute-macro members. -- **`StatementMacroSnapshot`** — for a function-like macro. It parses `keyword! { … }` and hands the brace body straight to the entry function. Used by `snapshot_delegate_components!` and its check-component siblings. -- **`DeriveMacroSnapshot`** — for a derive. It parses `#[derive(Keyword)]` followed by the item, and the entrypoint re-attaches the `#[derive(...)]` before calling the derive expander. Used by `snapshot_derive_has_field!` and the other derive members. - -The keyword each wrapper matches is a compile-time marker from [src/keywords.rs](../../../crates/macros/cgp-macro-test-util-lib/src/keywords.rs), built with `cgp-macro-core`'s `define_keyword!`, so the wrapper only accepts the attribute or macro name it is meant to pin. - -## Generated items - -A `snapshot_*!` invocation expands to the real generated code followed by a `#[test]` that binds the pretty-printed expansion to the declared name and runs the assertion. From this invocation — - -```rust -snapshot_cgp_component! { - #[cgp_component(FooProvider)] - pub trait CanDoFoo { - fn foo(&self, value: u32) -> String; - } - - expand_foo_component(output) { - insta::assert_snapshot!(output, @"…"); - } -} -``` - -— `MacroSnapshot::wrap_output` emits the component expansion verbatim (the consumer trait, provider trait, blanket impls, marker struct, and provider impls) and then: - -```rust -#[test] -fn expand_foo_component() { - let output = ""; - insta::assert_snapshot!(output, @"…"); -} -``` - -Because the expansion is emitted into the module, the snapshot participates in normal compilation exactly like a plain macro invocation would — adding or removing a snapshot changes only the golden assertion, never the compile or runtime coverage. The pretty-printed string is produced by `pretty_format` in [src/functions/pretty_format.rs](../../../crates/macros/cgp-macro-test-util-lib/src/functions/pretty_format.rs): it runs `cgp-macro-core`'s `strip_macro_prelude` over the tokens to drop the `::cgp::macro_prelude::` hygiene prefix, then formats with `prettyplease`, so the snapshot reads as the code a user would write rather than as fully-qualified hygienic output. - -## Behavior and corner cases - -The snapshot **runs the production macro**, not a copy: any change to a `cgp-macro-lib` entry function's output flows straight into the pinned snapshot string, which is the point — a diff in the golden output is the signal that the expansion changed. - -The **derive members re-emit the annotated item themselves**. Because a derive macro produces only the *added* code and not the struct or enum it decorates, an entrypoint like `snapshot_derive_has_field` prepends the original item to the output so the generated impls have a type to attach to. - -The **assertion is inline by convention**, following [crates/tests/AGENTS.md](../../../crates/tests/AGENTS.md): the snapshot string is written as an `insta` inline snapshot (`@"…"`) in the test file, so the golden output lives beside the invocation rather than in a separate `.snap` file. - -## Tests - -The snapshot macros are the test harness rather than the thing under test, so their coverage is the set of snapshot invocations across the suite. Per [crates/tests/AGENTS.md](../../../crates/tests/AGENTS.md), each CGP macro's expansion is snapshotted only in the concept target that owns its feature, and each owning target's snapshots are indexed by that macro's own implementation document. Representative usages: - -- [basic_delegation/component_macro.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/component_macro.rs) — the canonical `snapshot_cgp_component!` invocation, using the `AttributeMacroSnapshot` shape. -- [async_and_send/cgp_fn_async.rs](../../../crates/tests/cgp-tests/tests/async_and_send/cgp_fn_async.rs) — a `snapshot_cgp_fn!` over a stacked `#[cgp_fn]` / `#[async_trait]`. -- [dispatching/use_delegate_getter.rs](../../../crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs) — `snapshot_delegate_components!` and `snapshot_delegate_and_check_components!`, using the `StatementMacroSnapshot` shape. - -The per-macro Snapshots sections — for example [cgp_component.md](cgp_component.md) — are the canonical index of which expansion variants each family member pins and which are still missing. - -## Source - -- Proc-macro shims: [cgp-macro-test-util/src/lib.rs](../../../crates/macros/cgp-macro-test-util/src/lib.rs). -- Per-macro entrypoints: [cgp-macro-test-util-lib/src/entrypoints/](../../../crates/macros/cgp-macro-test-util-lib/src/entrypoints/). -- Snapshot wrapper types: [src/types/](../../../crates/macros/cgp-macro-test-util-lib/src/types/). -- Pretty-printer: [src/functions/pretty_format.rs](../../../crates/macros/cgp-macro-test-util-lib/src/functions/pretty_format.rs). -- Keyword markers: [src/keywords.rs](../../../crates/macros/cgp-macro-test-util-lib/src/keywords.rs). -- Each snapshot calls the matching production entry function in [cgp-macro-lib](../../../crates/macros/cgp-macro-lib/). diff --git a/docs/implementation/entrypoints/sum.md b/docs/implementation/entrypoints/sum.md deleted file mode 100644 index 21e42dec..00000000 --- a/docs/implementation/entrypoints/sum.md +++ /dev/null @@ -1,47 +0,0 @@ -# `Sum!` — implementation - -`Sum!` is a function-like macro that expands a comma-separated list of types into an `Either`/`Void` chain — the type-level coproduct CGP uses to represent an enum's variants. This document covers how the macro parses its list and emits the chain; for the accepted syntax and the full expansion a user sees, read the reference document [reference/macros/sum.md](../../reference/macros/sum.md). - -## Entry point - -The macro is driven by the thin `Sum` function in [cgp-macro-lib/src/sum.rs](../../../crates/macros/cgp-macro-lib/src/sum.rs), which parses the body into a `SumType`, calls its `eval`, and emits the result: - -```rust -let sum_type: SumType = parse2(body)?; -Ok(sum_type.eval()?.to_token_stream()) -``` - -The body is parsed as a comma-separated list of types, so a malformed element fails at `parse2`. The single AST type is documented in the [`sum` AST stack](../asts/sum.md). - -## Pipeline - -The macro is a parse-then-`eval` step with no further stages, mirroring `Product!` exactly but over a different spine. `SumType::parse` reads a `Punctuated`, and `eval` folds it into the chain and re-parses through [`parse_internal!`](../macros/parse_internal.md). The [`sum` AST document](../asts/sum.md) covers the type. - -## Generated items - -`Sum!` emits a single type: a right-nested chain of `Either` terminated by `Void`, one `Either` per element. - -```rust -// Sum![A, B, C] -Either>> -``` - -The chain is built by folding right-to-left onto `Void`, so an empty `Sum![]` is just `Void`. `Either` and `Void` are emitted through the [export markers](../../../crates/macros/cgp-macro-core/src/exports.rs). - -## Behavior and corner cases - -The only structural difference from `Product!` is the terminator: a sum folds onto `Void` rather than `Nil`. This is what makes an empty sum uninhabited — `Void` is an empty enum with no values, so an empty choice cannot be constructed, whereas an empty product (`Nil`) is a valid unit-like value. Otherwise the parsing is identical: a trailing comma is accepted through `parse_terminated`, and an empty body is valid. - -## Tests - -`Sum!` has no snapshot macro of its own; its expansion is exercised by a dedicated same-type assertion and by the enum field-derive that emits a `Sum!` of `Field` branches. - -- [extensible_variants/sum_macro.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/sum_macro.rs) writes `Sum![u32, String, bool]` and asserts it is the identical type to the hand-written `Either>>`, pinning the expansion directly, then builds values by nesting `Left`/`Right` to select each branch. -- [extensible_variants/has_fields_enum.rs](../../../crates/tests/cgp-tests/tests/extensible_variants/has_fields_enum.rs) exercises the `Sum!` variant spine an enum derives through `#[derive(HasFields)]`, where each branch is a `Field`. - -## Source - -- Entry point: `Sum` in [cgp-macro-lib/src/sum.rs](../../../crates/macros/cgp-macro-lib/src/sum.rs). -- `SumType` AST type: [cgp-macro-core/src/types/sum.rs](../../../crates/macros/cgp-macro-core/src/types/sum.rs), documented in [asts/sum.md](../asts/sum.md). -- The fold re-parses through [parse_internal!](../macros/parse_internal.md). -- Runtime types `Either` and the uninhabited `Void`: defined in [cgp-field](../../../crates/core/cgp-field/src/types/sum.rs). diff --git a/docs/implementation/entrypoints/symbol.md b/docs/implementation/entrypoints/symbol.md deleted file mode 100644 index 0892fe6e..00000000 --- a/docs/implementation/entrypoints/symbol.md +++ /dev/null @@ -1,54 +0,0 @@ -# `Symbol!` — implementation - -`Symbol!` is a function-like macro that expands a string literal into a type-level string — the `Symbol>` type CGP uses to name a field at compile time. This document covers how the macro parses the literal and emits that type; for the accepted syntax and the full expansion a user sees, read the reference document [reference/macros/symbol.md](../../reference/macros/symbol.md). - -## Entry point - -The macro is driven by the thin `Symbol` function in [cgp-macro-lib/src/symbol.rs](../../../crates/macros/cgp-macro-lib/src/symbol.rs), which parses the body into a `Symbol` construct and returns its token stream directly: - -```rust -let symbol: Symbol = parse2(body)?; -Ok(symbol.to_token_stream()) -``` - -There is no multi-stage pipeline here — a single `Parse` reads the literal and a single `ToTokens` emits the type, so all the logic lives in the one [`Symbol` AST type](../asts/symbol.md). A body that is not a string literal fails while parsing `Symbol`, since its `Parse` impl expects a `LitStr`. - -## Pipeline - -The macro is a single parse-then-emit step with no intermediate stages. `Symbol::parse` captures the literal's value and span, and `Symbol::to_tokens` folds that value into the `Symbol>` type. The [`Symbol` AST document](../asts/symbol.md) covers the type. - -## Generated items - -`Symbol!` emits a single type: the `Symbol` wrapper around a right-nested `Chars` chain terminated by `Nil`, with the string's byte length baked in as the leading const argument. The chain has one `Chars` node per character: - -```rust -// Symbol!("abc") -Symbol<3, Chars<'a', Chars<'b', Chars<'c', Nil>>>> -``` - -The chain is built by folding the characters right-to-left onto `Nil`, so an empty `Symbol!("")` collapses to `Symbol<0, Nil>`. The `Symbol`, `Chars`, and `Nil` names are emitted through the [export markers](../../../crates/macros/cgp-macro-core/src/exports.rs) so the expansion is hygienic. - -## Behavior and corner cases - -The leading `LEN` argument is the string's **byte** length, taken from `str::len()`, not its character count. For an ASCII string the two coincide, but a multi-byte string diverges: `Symbol!("世界你好")` records `12`, while the `Chars` chain has one node per Unicode scalar value, so four `Chars` nodes. This split is deliberate — the char count lives in the chain's shape and the byte length lives in `LEN`. - -Every emitted token carries the literal's span (via `quote_spanned!`), so a downstream type error points back at the `Symbol!` invocation rather than at the macro internals. - -The `Symbol` type is also constructed from a bare identifier rather than a literal by the data derives and the [`Path!` stack](../asts/path.md): a struct field, enum variant, or lowercase path segment becomes a `Symbol` through `Symbol::from_ident`, which reuses the same `ToTokens` emission. That constructor calls `Ident::unraw` first, so a raw-identifier field such as `r#type` is tagged by its logical name — `Symbol!("type")`, not the literal `r#type` — which is what lets a `Symbol!("type")` bound match it. This path does not go through this macro's `Parse` impl, which only accepts a `LitStr` and records its value verbatim (so `Symbol!("r#type")` would encode the literal `r#type`). - -## Known issues - -The `LEN` const argument exists to work around stable Rust's inability to compute the length of a `Chars` chain inside a const-generic context. Rather than deriving the length from the character list at the type level, the macro precomputes it and bakes it in as a separate parameter. This is why the length appears redundantly in every `Symbol` type and why it is a byte length rather than a character count; it is a limitation of the encoding, not a bug. - -## Tests - -The `Symbol!` expansion has no snapshot macro of its own; its behavior is exercised through runtime round-trip tests and, indirectly, through the field-derive snapshots that embed `Symbol` in their output. - -- [field_access/symbol.rs](../../../crates/tests/cgp-tests/tests/field_access/symbol.rs) checks that a `Symbol!` value `Display`s back to its string and that `StaticString::VALUE` recovers the original literal, covering the empty string, a single character, a multi-word string, and a multi-byte Unicode string — the last pinning that the char chain, not `LEN`, drives the reconstruction. -- [extensible_records/person_record.rs](../../../crates/tests/cgp-tests/tests/extensible_records/person_record.rs) pins, through a `snapshot_derive_cgp_data!` golden, how a multi-character field name such as `first_name` expands into its `Symbol>` spine with the leading length. - -## Source - -- Entry point: `Symbol` in [cgp-macro-lib/src/symbol.rs](../../../crates/macros/cgp-macro-lib/src/symbol.rs). -- `Symbol` AST type: [cgp-macro-core/src/types/field/symbol.rs](../../../crates/macros/cgp-macro-core/src/types/field/symbol.rs), documented in [asts/symbol.md](../asts/symbol.md). -- Runtime types `Symbol`, `Chars`, and `Nil`: defined in [cgp-base-types](../../../crates/core/cgp-base-types/src/types/). diff --git a/docs/implementation/functions/derive/delegated_impls.md b/docs/implementation/functions/derive/delegated_impls.md deleted file mode 100644 index 0b1fa7e1..00000000 --- a/docs/implementation/functions/derive/delegated_impls.md +++ /dev/null @@ -1,32 +0,0 @@ -# Delegated-impl synthesis - -The delegated-impl functions turn a trait's items into the body of an impl that forwards every method, associated type, and constant to a chosen delegate type. They are the shared machinery behind the forwarding impls that `#[cgp_component]` and its relatives emit — the consumer blanket impl, the provider blanket impl, the `UseContext` impl, and the `RedirectLookup` impl all route their calls through the same delegate type by way of these functions, so the forwarding shape is written once and reused everywhere. - -The core idea is a single delegate type supplied by the caller: given a trait and a `delegate_type` expression, each generated impl item calls the delegate's version of that trait item. A method `fn area(context: &Context) -> f64` becomes `fn area(context: &Context) -> f64 { ::area(context) }`; an associated type becomes `type Output = ::Output;`; an associated const becomes `const N: usize = ::N;`. Which concrete type fills `` is the caller's choice, and that is what distinguishes the four impls that use these functions. - -## `trait_items_to_delegated_impl_items` - -`trait_items_to_delegated_impl_items` maps a slice of `syn::TraitItem` to the `Vec` that forwards each one to the delegate, and is the entry point most callers use. It takes the trait items, the `delegate_type` to forward to, and the `provider_trait_path` used to qualify associated-type and const projections, and applies `trait_item_to_delegated_impl_items` to each item. - -`trait_item_to_delegated_impl_items` handles the three supported item kinds and rejects the rest. A **function** is forwarded by `signature_to_delegated_impl_item_fn`, which builds a method body that calls the delegate with the same arguments. An **associated type** becomes `type Name = ::Name;`, qualified through the provider trait path so the projection is unambiguous. An **associated const** becomes `const Name = ::Name;`, copying the const's type and attributes. Any other trait item — a macro invocation, for example — produces a `syn::Error` reading "unsupported trait item", so the macro fails cleanly rather than dropping the item silently. - -## `provider_trait_to_impl_items` - -`provider_trait_to_impl_items` is the convenience wrapper for the common case of forwarding a *provider* trait's own items to a delegate. It takes the provider `ItemTrait` and a `delegate_type`, reconstructs the provider trait path from the trait's identifier and its generics (via `split_for_impl`), and calls `trait_items_to_delegated_impl_items` with that path. The provider blanket impl and the `RedirectLookup` impl use it because both forward the provider trait to a `<… as DelegateComponent<…>>::Delegate` type; the consumer blanket impl and the `UseContext` impl call `trait_items_to_delegated_impl_items` directly instead, because they forward *across* traits (consumer to provider, or provider to consumer) and so must supply a different trait path than the trait being implemented. - -## Behavior and corner cases - -The forwarding qualifies associated types and consts through the supplied trait path, not through the impl's own trait, which matters when the source and target traits differ. When the consumer blanket impl forwards to the provider trait, an associated type is projected as `<__Context__ as ProviderTrait>::Output`, because the value lives on the provider side; passing the wrong path here would produce an impl that does not type-check. Method forwarding, by contrast, needs no trait qualification — it calls the delegate's inherent-looking associated function `::method(args)` and relies on the delegate's trait bound in the impl's `where` clause to resolve it. - -## Tests - -These functions have no dedicated unit test; they are covered through the `#[cgp_component]` expansion snapshots, which pin the forwarding bodies of all four impls. - -- The plain case in [basic_delegation/component_macro.rs](../../../../crates/tests/cgp-tests/tests/basic_delegation/component_macro.rs) shows method forwarding through `<__Provider__ as DelegateComponent<…>>::Delegate::foo(…)` and through `UseContext`/`RedirectLookup`. -- The default-method case in [basic_delegation/default_methods.rs](../../../../crates/tests/cgp-tests/tests/basic_delegation/default_methods.rs) confirms a forwarded call resolves to a default body. -- Associated-type and const forwarding are pinned by the `#[cgp_type]` snapshots in the `abstract_types` target. - -## Source - -- The functions live in [cgp-macro-core/src/functions/delegated_impls/](../../../../crates/macros/cgp-macro-core/src/functions/delegated_impls/): `trait_items.rs` holds `trait_items_to_delegated_impl_items` and the per-item dispatch, `provider_trait.rs` holds `provider_trait_to_impl_items`, `signature.rs` holds the method-forwarding builder `signature_to_delegated_impl_item_fn`, and `item_type.rs` holds `trait_to_impl_item_type`. -- The callers are documented in [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md) and the [cgp_component AST stack](../../asts/cgp_component.md). diff --git a/docs/implementation/functions/derive/generics.md b/docs/implementation/functions/derive/generics.md deleted file mode 100644 index 858ff6f0..00000000 --- a/docs/implementation/functions/derive/generics.md +++ /dev/null @@ -1,13 +0,0 @@ -# `merge_generics` - -`merge_generics` combines two `syn::Generics` into one, concatenating their parameters and unioning their `where` clauses. The CGP codegen repeatedly needs to build an impl whose generics come from more than one source — a trait's own generics plus a provider's extra parameters, say — and this helper is how those are joined into a single parameter list and predicate set. - -The merge is a straightforward concatenation: the parameters of the first `Generics` come before those of the second, and the predicates of both `where` clauses are collected into one (dropped entirely when neither side has any). The angle-bracket tokens are taken from the first argument, so parameter *order* follows the caller's chosen sequence — the first argument's parameters lead. There is no de-duplication or reordering, so the caller is responsible for passing parameter lists that are already free of clashes and in a valid order (lifetimes before type parameters, as Rust requires). - -## Tests - -- The helper has no dedicated test; it is covered indirectly through the expansion snapshots of the macros that assemble multi-source impls. - -## Source - -- The function lives in [cgp-macro-core/src/functions/generics/merge_generics.rs](../../../../crates/macros/cgp-macro-core/src/functions/generics/merge_generics.rs). diff --git a/docs/implementation/functions/derive/idents.md b/docs/implementation/functions/derive/idents.md deleted file mode 100644 index 5d7fc20c..00000000 --- a/docs/implementation/functions/derive/idents.md +++ /dev/null @@ -1,15 +0,0 @@ -# Identifier case conversion - -The case-conversion helpers turn identifiers between the naming conventions CGP's generated code uses — PascalCase for type and trait names, snake_case for values and method names. The macros derive one name from another all over the codebase (a provider trait name from a function name, a context value name from the context type name), and these functions are the shared way to do it. - -`to_camel_case_str` produces a PascalCase string by splitting on underscores, dropping empty segments, and upper-casing the first letter of each word. `to_snake_case_str` produces a snake_case string by inserting an underscore before each interior uppercase letter and lower-casing the result. - -The one non-obvious helper is `to_snake_case_ident`, which additionally wraps its result in the reserved `__…__` form: unless the identifier already starts with an underscore, `Context` becomes `__context__` rather than plain `context`. This is what produces the reserved value name that pairs with a reserved type name like `__Context__`, keeping generated bindings from clashing with a user's own identifiers — the same convention behind the reserved names described in [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md). - -## Tests - -- These helpers have no dedicated test; they are covered through the expansion snapshots, where the derived names appear in the generated code. - -## Source - -- The functions live in [cgp-macro-core/src/functions/camel_case.rs](../../../../crates/macros/cgp-macro-core/src/functions/camel_case.rs) and [cgp-macro-core/src/functions/snake_case.rs](../../../../crates/macros/cgp-macro-core/src/functions/snake_case.rs). diff --git a/docs/implementation/functions/parse/is_provider_params.md b/docs/implementation/functions/parse/is_provider_params.md deleted file mode 100644 index 1909d072..00000000 --- a/docs/implementation/functions/parse/is_provider_params.md +++ /dev/null @@ -1,24 +0,0 @@ -# `parse_is_provider_params` - -`parse_is_provider_params` converts a trait's generic parameters into the tuple of types that fills the `Params` position of an [`IsProviderFor`](../../../reference/traits/is_provider_for.md) bound. Every provider trait carries an `IsProviderFor` supertrait, and this function computes the `(Params)` part from the consumer trait's generics, so the marker records exactly the extra parameters a component takes beyond its context. - -The transformation is a straightforward per-parameter mapping over the trait's generics, emitting one type per parameter because the params tuple is a tuple of *types*. A type parameter passes through by name: `T` becomes `T`. A lifetime parameter is lifted into a type through the `Life` wrapper, because a bare lifetime cannot appear as a tuple element: `'a` becomes `Life<'a>`. Only the parameter's name matters, so any bounds or defaults on it are dropped. Each element is rendered with `parse_internal!`, and the result is a `Punctuated` that the caller wraps in parentheses. - -## Behavior and corner cases - -Lifetimes are preserved in the params tuple even though they are dropped from the redirected lookup path. `parse_is_provider_params` emits `Life<'a>` for a lifetime, so `HasReference<'a, T>` yields the tuple `(Life<'a>, T)`; the separate `generic_params_to_path` helper used by the `RedirectLookup` impl keeps only type parameters, which is why a lifetime appears in `IsProviderFor` but not in the `ConcatPath` path. Holding both facts together is necessary to read the lifetime-component snapshot correctly. - -A const generic parameter is rejected with a spanned `syn::Error`. A const value cannot appear in the params tuple (which is a tuple of types) and CGP's type-based wiring cannot key on it, so the `GenericParam::Const` arm returns an error rather than emitting a tuple element. Because every macro that builds a provider trait routes through this helper, the rejection applies uniformly to `#[cgp_component]`, `#[cgp_type]`, and `#[cgp_getter]`; the user-facing consequence is recorded in [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md). - -## Tests - -The params tuple is pinned by the expansion snapshots, and the const-parameter rejection has its own failure case. - -- The empty `()` case in [basic_delegation/component_macro.rs](../../../../crates/tests/cgp-tests/tests/basic_delegation/component_macro.rs). -- The `(Life<'a>, T)` case in [generic_components/component_lifetime.rs](../../../../crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs). -- The const-parameter rejection in [cgp-macro-tests/tests/parser_rejections/cgp_component.rs](../../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs). - -## Source - -- The function lives in [cgp-macro-core/src/functions/is_provider_params.rs](../../../../crates/macros/cgp-macro-core/src/functions/is_provider_params.rs). -- It is called by the provider-trait and blanket-impl builders in [cgp-macro-core/src/types/cgp_component/preprocessed/](../../../../crates/macros/cgp-macro-core/src/types/cgp_component/preprocessed/); the `Life` wrapper it emits is documented in [reference/types/life.md](../../../reference/types/life.md). diff --git a/docs/implementation/macros/define_keyword.md b/docs/implementation/macros/define_keyword.md deleted file mode 100644 index 9ac2a5d4..00000000 --- a/docs/implementation/macros/define_keyword.md +++ /dev/null @@ -1,18 +0,0 @@ -# `define_keyword!` - -`define_keyword!` declares a custom-keyword marker type for the CGP parsers — a zero-sized struct paired with the string it matches. The CGP macros parse several bespoke keywords in their bodies (`new` in `#[cgp_impl]` and `delegate_components!`, `open` in the `open` dispatch statement, and the rest), and each such keyword is represented at the type level by a struct that carries the keyword's spelling so the parser can recognize it and error on a mismatch. - -The macro expands to two items. `define_keyword!(Foo, "foo")` emits `pub struct Foo;` and an `impl crate::traits::IsKeyword for Foo` whose associated const `IDENT` is `"foo"`. The `IsKeyword` trait is the shared interface the parsing machinery uses: a parser peeks the next identifier, compares it against `::IDENT`, and consumes it as that keyword when they match. Defining a keyword is therefore just declaring the marker and wiring its spelling into the trait; the actual peek-and-consume logic lives with the parser that uses the keyword. - -## Behavior and corner cases - -The keyword string and the struct name are independent, so the marker can be named for its role rather than its spelling, though in practice they match (`New`/`"new"`, `Open`/`"open"`). Because the generated struct is an ordinary public type, a keyword marker can also appear in generated code or as a type-level tag where that is useful, not only in the parser. - -## Tests - -- `define_keyword!` has no dedicated test; the keywords it defines are exercised through the parser tests and expansion snapshots of the macros that use them — for example the `new`-prefixed forms pinned in the `basic_delegation` snapshots and the `open` statement pinned in the `namespaces` and `dispatching` targets. - -## Source - -- The macro is defined in [cgp-macro-core/src/macros/keyword.rs](../../../crates/macros/cgp-macro-core/src/macros/keyword.rs); the `IsKeyword` trait it implements lives in `cgp-macro-core/src/traits/`, and the keyword marker types that use it live in `cgp-macro-core/src/types/keyword*.rs`. -- The convention that custom keywords go through this macro is recorded in [cgp-macro-core/AGENTS.md](../../../crates/macros/cgp-macro-core/AGENTS.md). diff --git a/docs/implementation/macros/export_constructs.md b/docs/implementation/macros/export_constructs.md deleted file mode 100644 index 5b3a4a65..00000000 --- a/docs/implementation/macros/export_constructs.md +++ /dev/null @@ -1,16 +0,0 @@ -# `export_construct!` / `export_constructs!` - -`export_construct!` and `export_constructs!` declare the hygienic markers that let generated code refer to CGP library items by a short name that always expands to a fully-qualified path. Every CGP item the macros emit — `IsProviderFor`, `DelegateComponent`, `UseField`, `HasField`, and the rest — is represented in the codegen by a zero-sized marker struct whose `ToTokens` emits `::cgp::macro_prelude::`, so a user only needs `cgp` in scope for the expansion to resolve. - -`export_construct!(Name)` declares `pub struct Name;` and an impl of `quote::ToTokens` that emits `::cgp::macro_prelude::Name`. The two-argument form `export_construct!(From => To)` emits a different target path than the marker's own name, for the case where the codegen name and the exported item name differ. `export_constructs!` is the plural convenience wrapper: it takes a comma-separated list of `Name` or `From => To` entries and expands each through `export_construct!`, which is how [`exports.rs`](../../../crates/macros/cgp-macro-core/src/exports.rs) declares the whole set in one block. - -The point of the indirection is hygiene. Generated code interpolates the marker (`#is_provider_for`) rather than a literal `::cgp::...` path, so the emitted tokens carry the fully-qualified path without the macro author writing it out and without depending on what the user has imported. To emit a new CGP item from a macro, add it to the `export_constructs!` list in `exports.rs` and interpolate its marker; do not write `::cgp::...` path literals inline. - -## Tests - -- These macros have no dedicated test; they are exercised by every expansion snapshot in the suite, since the fully-qualified paths in generated code all originate from these markers. - -## Source - -- The macros are defined in [cgp-macro-core/src/macros/export.rs](../../../crates/macros/cgp-macro-core/src/macros/export.rs); the marker set they generate lives in [cgp-macro-core/src/exports.rs](../../../crates/macros/cgp-macro-core/src/exports.rs), and the `::cgp::macro_prelude` re-export surface is what makes the emitted paths resolve. -- The convention that all CGP items are referenced through these markers is recorded in [cgp-macro-core/AGENTS.md](../../../crates/macros/cgp-macro-core/AGENTS.md). diff --git a/docs/implementation/macros/parse_internal.md b/docs/implementation/macros/parse_internal.md deleted file mode 100644 index 29a98318..00000000 --- a/docs/implementation/macros/parse_internal.md +++ /dev/null @@ -1,23 +0,0 @@ -# `parse_internal!` - -`parse_internal!` is the internal macro the CGP codegen uses to build a `syn` AST node from quasi-quoted tokens, attaching a descriptive error if the tokens do not parse. It is the workhorse of the whole macro implementation: nearly every generated fragment — a `where` predicate, a trait path, a type, an impl item — is produced by quoting tokens and parsing them into the target `syn` type through this macro, rather than by constructing the `syn` node field-by-field. - -The macro is a thin wrapper over a function of the same name. `parse_internal!( #tokens … )` expands to `crate::functions::parse_internal(crate::vendor::quote!( #tokens … ))?` — it quotes its body with `quote!` (routed through the crate's vendored re-export so exported `macro_rules!` can reach it) and passes the resulting `TokenStream` to the `parse_internal` function. The trailing `?` is significant: the macro expands to a `?` expression, so it must be invoked inside a function that returns `syn::Result`, and a parse failure propagates as an early return rather than a panic. - -The `parse_internal` function is where the descriptive error is attached. It calls `syn::parse2` for the inferred target type `T: Parse`, and on failure combines the original parser error with a second error spanning the input tokens and reading "failed to parse internal tokens to type ``", followed by the offending tokens rendered with the `::cgp::macro_prelude::` prefix stripped for readability (via `strip_macro_prelude`). This turns an opaque "expected …" parser error into one that names both the target AST type and the concrete tokens that failed, which is what makes a codegen bug diagnosable. - -## Behavior and corner cases - -The target type is inferred from context, so the same invocation parses into whatever `syn` type the surrounding code expects. `let ty: Type = parse_internal!(#ident);` parses a type while `let p: Path = parse_internal!(#ident #generics);` parses a path from similar tokens; the `T: Parse` bound is resolved by the binding or argument position. When no type can be inferred, annotate the binding. - -A subtle interaction with the `?` expansion is that `parse_internal!` cannot be used where a `?` is not valid — outside a `syn::Result`-returning function, or in a `const` context. The sibling plain function `parse_internal(tokens)?` is available for the cases where the token stream is already built (for example when a `quote!` block was assembled conditionally), and the blanket-impl builder uses it directly for exactly that reason. - -## Tests - -- `parse_internal!` has no dedicated test; it is exercised by every macro-expansion snapshot in the suite, since essentially all generated code passes through it. Its error path is observed indirectly whenever a codegen change produces unparseable tokens during development. - -## Source - -- The macro is defined in [cgp-macro-core/src/macros/parse.rs](../../../crates/macros/cgp-macro-core/src/macros/parse.rs) and the backing function in [cgp-macro-core/src/functions/parse_internal.rs](../../../crates/macros/cgp-macro-core/src/functions/parse_internal.rs). -- The `quote!` re-export it depends on is in `cgp-macro-core/src/vendor.rs`, and the prefix-stripping helper is `strip_macro_prelude` in `cgp-macro-core/src/functions/strip.rs`. -- The convention that all AST nodes are built through this macro is recorded in [cgp-macro-core/AGENTS.md](../../../crates/macros/cgp-macro-core/AGENTS.md). diff --git a/docs/reference/README.md b/docs/reference/README.md deleted file mode 100644 index cd0830d1..00000000 --- a/docs/reference/README.md +++ /dev/null @@ -1,203 +0,0 @@ -# CGP Construct Reference - -This directory documents every CGP construct — one self-contained document per construct, each explaining its purpose, syntax or definition, expansion or behavior, examples, related constructs, and source. The documents are written for agents who need precise per-construct semantics, and they point only at library source, never at a test. The high-level conceptual framing that connects the constructs lives in the sibling [concepts/](../concepts/README.md) directory; the internal mechanics of each macro — its pipeline, the functions that synthesize its output, and every pointer into the test suite — live in the sibling [implementation/](../implementation/README.md) directory, to which each reference document's Source section links; the `/cgp` skill remains a complementary teaching aid. The authoring rules, document template, and the requirement to keep these documents in sync with the code live in [../AGENTS.md](../AGENTS.md). - -## Summary - -This section summarizes every documented construct, grouped by the job it does and ordered from the constructs almost every CGP task needs down to the specialized and reference-only ones. Each group says when to reach for it and links to the per-construct document that carries the full grammar, expansion, and examples. The summary tells you only what a construct is *for*, not how it behaves in every corner, so read the linked document before writing, changing, reviewing, or debugging code that uses the construct — that document is the ground truth, and this catalog exists to route you to the right one. - -### Core constructs - -These are the constructs behind almost every CGP program: defining a component, writing a provider for it, and wiring a context to the provider it uses. [`#[cgp_component]`](macros/cgp_component.md) turns one trait into a component — the consumer trait callers invoke, the provider trait implementers target, and the blanket impls plus component-name marker that connect them. A provider is then written with [`#[cgp_impl]`](macros/cgp_impl.md), the idiomatic form that keeps `self`/`Self` and consumer-trait signatures and desugars into the inside-out provider-trait shape; its lower-level layers [`#[cgp_provider]`](macros/cgp_provider.md) and [`#[cgp_new_provider]`](macros/cgp_new_provider.md) implement the provider trait directly (the latter also declaring the provider struct) and are mostly what you read rather than write. When a capability has a single implementation and needs no wiring, [`#[cgp_fn]`](macros/cgp_fn.md) generates a blanket-impl trait straight from a function, and [`#[blanket_trait]`](macros/blanket_trait.md) does the same from a trait with default methods and supertrait dependencies; [`#[async_trait]`](macros/async_trait.md) rewrites a trait's `async fn` declarations into the lint-clean `-> impl Future` form that CGP's async methods use. - -Wiring is where a concrete context chooses its providers. [`delegate_components!`](macros/delegate_components.md) builds a context's type-level table mapping each component to a provider — read its document for the array-key, generic-list, aggregate-provider (`new`), and per-type `open` forms — and that table is the [`DelegateComponent`](traits/delegate_component.md) trait underneath, a compile-time key→value map that both ordinary wiring and inner dispatch tables share. Two providers appear directly in wiring: [`UseContext`](providers/use_context.md) satisfies a provider trait by routing back through the context's own consumer-trait impl (and is the default inner provider of a higher-order provider), and [`UseDefault`](providers/use_default.md) selects a component's default method bodies. To dispatch one component per value of a generic parameter, prefer the `open` statement of `delegate_components!`; the legacy path is the [`UseDelegate`](providers/use_delegate.md) provider and the [`#[derive_delegate]`](attributes/derive_delegate.md) attribute that generates its dispatch impl, which you still meet in existing code and in CGP's own error and handler components. - -### Basic field access - -Reach for these whenever a provider needs a value out of its context — the most common form of dependency injection in CGP. [`#[derive(HasField)]`](derives/derive_has_field.md) gives a struct per-field getters keyed by a type-level tag, implementing the [`HasField`](traits/has_field.md) trait (and its mutable and provider-side mirrors) that all value injection stands on; the tags are [`Symbol!`](macros/symbol.md) type-level strings for named fields and [`Index`](types/index.md) type-level numbers for tuple fields. The default way to read such a field is an [`#[implicit]`](attributes/implicit.md) argument, which looks like an ordinary function parameter but is sourced from a same-named context field, and whose access rules (`.clone()` for owned, `.as_str()` for `&str`, and the option/slice/mutable forms) live in its document. - -A getter trait is the sparing alternative for the cases an implicit argument cannot reach — a field on another type, a named shared capability, or a getter carrying an inferred associated type. [`#[cgp_auto_getter]`](macros/cgp_auto_getter.md) generates a blanket getter impl over `HasField` keyed by the method name, while the advanced [`#[cgp_getter]`](macros/cgp_getter.md) makes the getter a full component so its source field is chosen at wiring time through the [`UseField`](providers/use_field.md) provider — with [`UseFieldRef`](providers/use_field_ref.md) for `AsRef`/`AsMut` access and [`UseFields`](providers/use_fields.md) for the method-name convention. [`ChainGetters`](providers/chain_getters.md) composes getters to reach a field on a nested context, and [`MRef`](types/mref.md) is the owned-or-borrowed getter return type for a getter that may lend or produce its value. - -### Imports - -These attributes declare a provider's dependencies as if importing them, keeping the constraints off the public trait interface. [`#[uses]`](attributes/uses.md) adds `Self:` capability bounds that read like a `use` statement — the idiomatic replacement for a hand-written `where Self: Trait` clause — and [`#[use_provider]`](attributes/use_provider.md) completes an inner provider's bound in a higher-order provider by filling in the stray `` context argument the provider trait requires. [`#[extend]`](attributes/extend.md) adds a supertrait to the generated trait (the `pub use` counterpart of `#[uses]`, and the only way to add a supertrait in `#[cgp_fn]`), while [`#[extend_where]`](attributes/extend_where.md) promotes a `where` predicate onto a `#[cgp_fn]` trait's own definition rather than only its impl. The `#[impl_generics(...)]` attribute, which adds a bounded generic parameter to a `#[cgp_fn]` impl alone, is documented within [`#[cgp_fn]`](macros/cgp_fn.md). - -### Abstract types - -Use these when generic code must name a type — an error type, a scalar, a runtime — that each context chooses for itself. [`#[cgp_type]`](macros/cgp_type.md) defines an abstract-type component from a trait with one associated type, layering a [`UseType`](providers/use_type.md) blanket impl on top of [`#[cgp_component]`](macros/cgp_component.md) so a context binds the concrete type by wiring the component to `UseType`; that machinery rests on CGP's single built-in abstract-type component, [`HasType` / `TypeProvider`](components/has_type.md). The [`#[use_type]`](attributes/use_type.md) attribute — distinct from the `UseType` provider despite the shared name — imports an abstract type into a `#[cgp_fn]`/`#[cgp_impl]`/`#[cgp_component]` definition, rewriting a bare `Error` or `Scalar` into its fully-qualified `::Type` form and adding the supertrait or bound, and it also carries the equality form that pins an abstract type to a concrete one. [`UseDelegatedType`](providers/use_delegated_type.md) resolves an abstract type through a lookup table instead of a fixed type, and [`WithProvider`](providers/with_provider.md) (with its `WithType`/`WithField`/`WithContext`/`WithDelegatedType` aliases) is the adapter that lets a foundational `TypeProvider` or `FieldGetter` stand in as a named component's provider. - -### Error handling - -CGP makes the error type abstract so fallible generic code never names a concrete error, and these components carry that strategy. [`HasErrorType`](components/has_error_type.md) gives a context one shared `Error` type (an abstract-type component, so wired with `UseType`), and [`CanRaiseError` / `CanWrapError`](components/can_raise_error.md) construct that error from a source error and attach detail to it, dispatching per source or detail type. The interchangeable strategies that satisfy them — the [error providers](providers/error_providers.md) `RaiseFrom`, `ReturnError`, `RaiseInfallible`, `DebugError`/`DisplayError`, `DiscardDetail`, and `PanicOnError` — stay generic over the context's error type, while the concrete backends (`cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`) are opt-in and named in their own crates. The wiring keys and backend providers are deliberately not in the prelude and must be imported from `cgp::core::error` / `cgp::extra::error`. - -### Checks and debugging - -CGP wiring is lazy, so a context can compile while wired wrong; these constructs force that failure to surface, readably, at the wiring site. [`check_components!`](macros/check_components.md) asserts at compile time that a context can use each listed component, and [`delegate_and_check_components!`](macros/delegate_and_check_components.md) fuses wiring and checking for basic contexts (but never for an aggregate provider). Both build on two marker traits: [`CanUseComponent`](traits/can_use_component.md), the context-side check that a context both delegates a component and its provider's dependencies hold, and [`IsProviderFor`](traits/is_provider_for.md), the supertrait every provider trait carries that re-exposes a provider's `where` bounds so the compiler names the actual missing dependency instead of a bare "trait not implemented". The `#[check_providers(...)]` form of `check_components!` asserts `IsProviderFor` on each provider directly, which is how a broken layer of a higher-order provider stack is localized. - -### Namespaces - -Namespaces are reusable, inheritable wiring tables — CGP's preset mechanism — for keeping top-level wiring short as component counts grow. [`cgp_namespace!`](macros/cgp_namespace.md) defines a namespace (optionally inheriting a parent); a context joins it with a `namespace` header inside [`delegate_components!`](macros/delegate_components.md), a component registers into one with the `#[prefix(...)]` attribute, and a provider registers as a per-type default with `#[default_impl(...)]` — the last two documented within `cgp_namespace!` and [`DefaultNamespace`](traits/default_namespace.md). The mechanism underneath is the [`RedirectLookup`](providers/redirect_lookup.md) provider, which re-routes a lookup along a type-level [`Path!`](macros/path.md) / [`PathCons`](types/path_cons.md), together with the [`DefaultNamespace` / `DefaultImpls`](traits/default_namespace.md) traits that resolve inherited and per-type defaults. The lightweight `open` statement of `delegate_components!` is a special case of the same redirection for per-type dispatch wired directly on one context. - -### Handlers and computation - -This family models computation as swappable components along the axes of sync/async, fallible/infallible, and input/input-free — reach for it for pipelines, I/O, and type-level interpreters. [`Computer`](components/computer.md) is the pure synchronous transform (with by-reference and async variants), [`TryComputer`](components/try_computer.md) adds fallibility, [`Handler`](components/handler.md) is the general async-and-fallible workhorse, and [`Producer`](components/producer.md) is the input-free case; [`CanRun` / `CanSendRun`](components/runner.md) run tasks and [`HasRuntime` / `HasRuntimeType`](components/has_runtime.md) supply the abstract runtime they execute against. A provider is written from a plain function with [`#[cgp_computer]`](macros/cgp_computer.md) or [`#[cgp_producer]`](macros/cgp_producer.md), which also wire the promotion tables that let one function answer the whole family, and [`#[cgp_auto_dispatch]`](macros/cgp_auto_dispatch.md) generates a variant-dispatching handler from a per-type trait. The providers that build and route handlers are the [handler combinators](providers/handler_combinators.md) (`ComposeHandlers`, `PipeHandlers`, `ReturnInput`, the `Promote*` lifts, and `UseInputDelegate`), the [dispatch combinators](providers/dispatch_combinators.md) that match enum variants and assemble records, and the [monad providers](providers/monad_providers.md) (`PipeMonadic` with the ident/ok/err monads) built on the [monad traits](traits/monad.md) for short-circuiting pipelines. - -### Extensible data types - -These derives and traits let generic code build and read structs and enums by their named fields and variants without naming the concrete type. [`#[derive(CgpData)]`](derives/derive_cgp_data.md) is the umbrella derive; its struct- and enum-specific faces are [`#[derive(CgpRecord)]`](derives/derive_cgp_record.md) and [`#[derive(CgpVariant)]`](derives/derive_cgp_variant.md), and its individual slices are [`#[derive(HasFields)]`](derives/derive_has_fields.md) (the whole-shape view), [`#[derive(BuildField)]`](derives/derive_build_field.md) (the record builder), [`#[derive(ExtractField)]`](derives/derive_extract_field.md) (the variant extractor), and [`#[derive(FromVariant)]`](derives/derive_from_variant.md) (variant construction). The traits behind them are [`HasFields`](traits/has_fields.md), the incremental-builder family in [`HasBuilder`](traits/has_builder.md), the extractor family in [`ExtractField`](traits/extract_field.md), [`FromVariant`](traits/from_variant.md), the presence markers of [`MapType`](traits/map_type.md), the list algebra of [`AppendProduct` / `ConcatProduct` / `MapFields`](traits/product_ops.md), the structural casts [`CanUpcast` / `CanDowncast` / `CanBuildFrom`](traits/cast.md), and the [optional-field extensions](traits/optional_fields.md) for defaulted and optional fields. Each entry in the shape is a [`Field`](types/field.md) pairing a value with its type-level name tag. - -### Type-level primitives - -These are the type-level building blocks the rest of CGP is constructed from — mostly written through sugar, and otherwise needing only to be recognized in expansions and error messages. The construction macros are [`Symbol!`](macros/symbol.md) (a type-level string, for field names), [`Product!`](macros/product.md) and [`Sum!`](macros/sum.md) (type-level record and variant lists), and [`Path!`](macros/path.md) (a routing path); their expanded spines are [`Cons` / `Nil`](types/cons.md) for products, [`Either` / `Void`](types/either.md) for sums, [`Chars`](types/chars.md) for the string behind `Symbol`, and [`PathCons`](types/path_cons.md) for paths. Two further lifts make non-type things addressable in trait resolution — [`Index`](types/index.md) for a tuple-field position and [`Life`](types/life.md) for a lifetime — and [`MRef`](types/mref.md) is the owned-or-borrowed getter value. The [`StaticFormat` / `StaticString` / `ConcatPath`](traits/static_format.md) traits recover these type-level strings and paths back into runtime data. - -## Directory layout - -The documents are grouped into subdirectories by the *kind* of construct, so a reader looking for "the macro I invoke", "the trait the macro generates", or "the provider I wire" each has an obvious place to start. A new document goes in the subdirectory that matches what the construct is; when you add one, place it accordingly and register it in the matching section below. The high-level conceptual overviews that tie multiple constructs together — the consumer/provider duality, dependency injection, namespaces, handlers, and so on — live in the sibling [concepts/](../concepts/README.md) directory rather than here, each pointing into these per-construct documents for the mechanics. - -The [macros/](macros/) directory holds the procedural macros a programmer invokes directly: the attribute macros that define components and providers, the function-like macros that wire and check them, and the type-level construction macros (`Symbol!`, `Product!`, `Sum!`, `Path!`). The [derives/](derives/) directory holds the `#[derive(...)]` macros, a distinct family large enough to warrant its own space. The [attributes/](attributes/) directory holds the modifier attributes that refine what the definition macros generate — they are not standalone macros but options consumed by a host macro such as `#[cgp_fn]` or `#[cgp_impl]`. - -The remaining directories hold the runtime library constructs the macros expand into. The [components/](components/) directory documents the built-in CGP components CGP ships with — full consumer/provider trait pairs such as `HasType`, `HasErrorType`, and the handler family — that an application consumes and wires like any component it defines itself. The [providers/](providers/) directory documents the zero-sized provider structs that appear in wiring — `UseField`, `UseType`, `UseDelegate`, `UseContext`, and the rest — the values a context delegates a component to. The [traits/](traits/) directory documents the capability and mechanism traits that are *not* themselves components: the wiring traits (`DelegateComponent`, `IsProviderFor`, `CanUseComponent`), the field and type capabilities (`HasField`, `HasFields`), the extensible-data builder and extractor families, and the type-level operations. The [types/](types/) directory documents the type-level building-block types the rest of CGP is constructed from (`Field`, `Index`, the `Cons`/`Nil` product spine, the `Either`/`Void` sum spine, and the `Chars`/`PathCons` lists). - -The distinction between [components/](components/) and [traits/](traits/) is whether the trait is a CGP component: a document belongs in `components/` when its trait is defined with `#[cgp_component]`, `#[cgp_type]`, or `#[cgp_getter]` and therefore has a generated provider trait and `…Component` marker that contexts wire; it belongs in `traits/` when it is an ordinary capability or mechanism trait that the machinery uses but no one delegates. - -This index is the catalog of constructs. When you add, remove, or rename a construct, update both its document and this index in the same change. Because documents live in different subdirectories, a cross-link between two of them is a relative path — a sibling in the same directory is `name.md`, and a document in another directory is `../that-dir/name.md`. - -## Tooling — [cargo-cgp.md](cargo-cgp.md) - -One document here describes a tool rather than a construct, and it is the exception to the per-construct rule above: it has no subdirectory and no Syntax/Expansion shape. It is registered here because a reader looking up how to *read* a CGP error belongs in the reference. - -- [`cargo-cgp`](cargo-cgp.md) — CGP's first-class error toolchain: the cargo subcommand that rewrites CGP compiler errors into a readable, root-cause-first form, how to install and run it, how its `[CGP-Exxx]` output maps to the [error catalog](../errors/README.md), and its companion `expand` command, which shows the ordinary Rust a target's CGP macros generate with the type-level constructs resugared. Recommend it for building, checking, and debugging CGP code. - -## Component definition macros — [macros/](macros/) - -These macros define CGP components and the providers that implement them — the core act of writing CGP code. - -- [`#[cgp_component]`](macros/cgp_component.md) — turn a trait into a component (consumer trait, provider trait, blanket impls). -- [`#[cgp_impl]`](macros/cgp_impl.md) — write a provider for a component using consumer-trait-style syntax. -- [`#[cgp_provider]`](macros/cgp_provider.md) — write a provider by implementing the provider trait directly. -- [`#[cgp_new_provider]`](macros/cgp_new_provider.md) — `#[cgp_provider]` that also defines the provider struct. -- [`#[cgp_fn]`](macros/cgp_fn.md) — define a single-implementation capability as a blanket-impl trait from a function. -- [`#[async_trait]`](macros/async_trait.md) — rewrite a trait's `async fn` declarations to `-> impl Future`, the lint-clean way to declare async CGP methods. -- [`#[cgp_type]`](macros/cgp_type.md) — define an abstract-type component. -- [`#[cgp_getter]`](macros/cgp_getter.md) — define a getter component wired through CGP. -- [`#[cgp_auto_getter]`](macros/cgp_auto_getter.md) — define a getter as a blanket impl over `HasField`. -- [`#[blanket_trait]`](macros/blanket_trait.md) — generate a blanket impl from a trait with default methods. -- [`#[cgp_computer]`](macros/cgp_computer.md) — define a `Computer` provider from a function. -- [`#[cgp_producer]`](macros/cgp_producer.md) — define a `Producer` provider from a function. -- [`#[cgp_auto_dispatch]`](macros/cgp_auto_dispatch.md) — generate a handler that dispatches over an extensible-data input. - -## Wiring and checking macros — [macros/](macros/) - -These macros connect components to providers on a concrete context and verify the wiring at compile time. - -- [`delegate_components!`](macros/delegate_components.md) — build a context's type-level table mapping components to providers. -- [`check_components!`](macros/check_components.md) — assert at compile time that a context's wiring is complete. -- [`delegate_and_check_components!`](macros/delegate_and_check_components.md) — delegate and check in one macro. -- [`#[cgp_namespace]`](macros/cgp_namespace.md) — group components under a namespace for presets and inheritance. - -## Type-level construction macros — [macros/](macros/) - -These macros construct the type-level vocabulary — strings, lists, sums, and paths — that the rest of CGP is built on. - -- [`Symbol!`](macros/symbol.md) — type-level string, used for field names. -- [`Product!` / `product!`](macros/product.md) — type-level list type and value. -- [`Sum!`](macros/sum.md) — type-level sum (variant) type. -- [`Path!`](macros/path.md) — type-level path, used by namespaces and redirected lookups. - -## Attribute modifiers — [attributes/](attributes/) - -These attributes refine what the definition macros generate and are used inside `#[cgp_impl]`, `#[cgp_fn]`, and `#[cgp_component]`. - -- [`#[implicit]`](attributes/implicit.md) — extract a function argument from a context field automatically. -- [`#[uses]`](attributes/uses.md) — import other CGP capabilities as `Self` bounds. -- [`#[use_type]`](attributes/use_type.md) — import an abstract associated type with fully-qualified rewriting. -- [`#[use_provider]`](attributes/use_provider.md) — complete an inner provider's bound in higher-order providers. -- [`#[extend]`](attributes/extend.md) — add supertrait bounds to a generated trait. -- [`#[extend_where]`](attributes/extend_where.md) — add `where` clauses to a generated trait definition. -- [`#[derive_delegate]`](attributes/derive_delegate.md) — generate `UseDelegate` providers that dispatch on a generic parameter. - -Three further modifier attributes do not yet have their own page and are documented inside their host construct's document; each is a candidate for a dedicated page here. - -- `#[impl_generics(...)]` — add bounded generic parameters to a `#[cgp_fn]`'s impl only (not its trait); documented in [`#[cgp_fn]`](macros/cgp_fn.md). -- `#[prefix(...)]` — register a `#[cgp_component]` trait into a namespace under a path; documented in [`#[cgp_namespace]`](macros/cgp_namespace.md). -- `#[default_impl(...)]` — register a `#[cgp_impl]` provider as a namespace's per-type default; documented in [`DefaultNamespace`](traits/default_namespace.md). - -## Data derives — [derives/](derives/) - -These derive macros generate the field-access and extensible-data machinery for structs and enums. - -- [`#[derive(HasField)]`](derives/derive_has_field.md) — per-field accessors keyed by `Symbol!`/`Index`. -- [`#[derive(HasFields)]`](derives/derive_has_fields.md) — whole-struct/enum field-list view. -- [`#[derive(CgpData)]`](derives/derive_cgp_data.md) — full extensible-data derivation. -- [`#[derive(CgpRecord)]`](derives/derive_cgp_record.md) — extensible record (struct) derivation. -- [`#[derive(CgpVariant)]`](derives/derive_cgp_variant.md) — extensible variant (enum) derivation. -- [`#[derive(BuildField)]`](derives/derive_build_field.md) — builder support for records. -- [`#[derive(ExtractField)]`](derives/derive_extract_field.md) — extractor support for variants. -- [`#[derive(FromVariant)]`](derives/derive_from_variant.md) — variant-construction support. - -## Built-in components — [components/](components/) - -These are the full CGP components CGP ships with — each a consumer trait, provider trait, and `…Component` marker — that an application wires through `delegate_components!` like any component it defines itself. - -- [`HasType` / `TypeProvider`](components/has_type.md) — CGP's built-in abstract-type component. -- [`HasErrorType`](components/has_error_type.md) — the abstract error type component. -- [`CanRaiseError` / `CanWrapError`](components/can_raise_error.md) — raising and wrapping source errors into the abstract error type. -- [`Computer` / `CanCompute`](components/computer.md) — the synchronous computation component and its by-reference and async variants. -- [`TryComputer` / `CanTryCompute`](components/try_computer.md) — the fallible computation component. -- [`Handler` / `CanHandle`](components/handler.md) — the general async, fallible, error-aware computation component. -- [`Producer` / `CanProduce`](components/producer.md) — the input-free production component. -- [`CanRun` / `CanSendRun`](components/runner.md) — the task-running components. -- [`HasRuntime` / `HasRuntimeType`](components/has_runtime.md) — the abstract runtime type and accessor components. - -## Providers — [providers/](providers/) - -These are the zero-sized provider structs a context delegates components to. They carry no runtime value and exist only at the type level. - -- [`UseContext`](providers/use_context.md) — satisfy a provider trait by routing back through the context's own consumer-trait impl. -- [`UseDelegate`](providers/use_delegate.md) — dispatch on a generic parameter through an inner type-level table. -- [`UseDelegatedType`](providers/use_delegated_type.md) — resolve an abstract type through an inner table. -- [`UseField`](providers/use_field.md) — implement a getter by reading a named context field. -- [`UseFieldRef`](providers/use_field_ref.md) — implement a getter by reading a field through `AsRef`/`AsMut`. -- [`UseFields`](providers/use_fields.md) — getter provider keyed by the method name. -- [`UseType`](providers/use_type.md) — supply a concrete type for an abstract-type component. -- [`UseDefault`](providers/use_default.md) — marker provider selecting default implementations. -- [`WithProvider`](providers/with_provider.md) — adapt a foundational provider into a component (and the `WithContext`/`WithType`/`WithField` aliases). -- [`RedirectLookup`](providers/redirect_lookup.md) — re-route a lookup along a type-level path; the namespace mechanism. -- [`ChainGetters`](providers/chain_getters.md) — chain field getters to reach into nested contexts. -- [Handler combinators](providers/handler_combinators.md) — `ComposeHandlers`, `PipeHandlers`, `ReturnInput`, and the `Promote*` adapters that build and lift handlers. -- [Dispatch combinators](providers/dispatch_combinators.md) — `MatchWithHandlers`, `MatchWithValueHandlers`, `ExtractFieldAndHandle`, and the rest of the cgp-dispatch routing providers. -- [Monad providers](providers/monad_providers.md) — `PipeMonadic`, `BindOk`, `BindErr`, and the identity/ok/err monad markers. -- [Error providers](providers/error_providers.md) — `DebugError`, `DisplayError`, `RaiseFrom`, `ReturnError`, and the other backends for the error components. - -## Runtime traits — [traits/](traits/) - -These are the capability and mechanism traits the macros expand into — the traits a programmer rarely writes by hand but must understand to read generated code. - -- [`DelegateComponent`](traits/delegate_component.md) — the per-context type-level table mapping a component key to a provider. -- [`IsProviderFor`](traits/is_provider_for.md) — the marker supertrait that surfaces missing-dependency errors. -- [`CanUseComponent`](traits/can_use_component.md) — the consumer-side check that a context can use a component. -- [`HasField`](traits/has_field.md) — tag-keyed field access (with `HasFieldMut` and the provider-side `FieldGetter`). -- [`HasFields`](traits/has_fields.md) — the whole-shape field representation and its conversions. -- [`HasBuilder`](traits/has_builder.md) — the incremental-builder trait family (`BuildField`, `UpdateField`, `FinalizeBuild`, …). -- [`ExtractField`](traits/extract_field.md) — the incremental-extractor trait family (`HasExtractor`, `FinalizeExtract`, …). -- [`FromVariant`](traits/from_variant.md) — generic construction of an enum from a named variant. -- [`MapType`](traits/map_type.md) — the present/absent/void type-mapping markers (`IsPresent`, `IsNothing`, …) and transforms. -- [`AppendProduct`](traits/product_ops.md) — type-level product operations (`AppendProduct`, `ConcatProduct`, `MapFields`). -- [`CanUpcast`](traits/cast.md) — structural casts between records and variants (`CanUpcast`, `CanDowncast`, `CanBuildFrom`). -- [`DefaultNamespace`](traits/default_namespace.md) — the namespace/preset default-resolution traits. -- [`StaticFormat`](traits/static_format.md) — runtime formatting of type-level strings and path concatenation. -- [Monad traits](traits/monad.md) — `MonadicTrans`, `MonadicBind`, `LiftValue`, and `ContainsValue`, the trait layer behind monadic handler composition. -- [Optional fields](traits/optional_fields.md) — the cgp-field-extra builder/extractor traits for optional and defaulted fields. - -## Type-level types — [types/](types/) - -These are the type-level building-block types the macros and traits operate on. - -- [`Field`](types/field.md) — a value paired with its type-level name tag. -- [`Index`](types/index.md) — a type-level natural number, used to tag tuple-struct fields. -- [`Cons` / `Nil`](types/cons.md) — the product (record) list spine. -- [`Either` / `Void`](types/either.md) — the sum (variant) list spine. -- [`Chars`](types/chars.md) — the type-level character list behind `Symbol`. -- [`PathCons`](types/path_cons.md) — the type-level path list behind `Path!`. -- [`Life`](types/life.md) — a lifetime lifted into a type. -- [`MRef`](types/mref.md) — an owned-or-borrowed value. diff --git a/docs/reference/attributes/derive_delegate.md b/docs/reference/attributes/derive_delegate.md deleted file mode 100644 index 1993af20..00000000 --- a/docs/reference/attributes/derive_delegate.md +++ /dev/null @@ -1,154 +0,0 @@ -# `#[derive_delegate]` - -`#[derive_delegate]` is an attribute on a `#[cgp_component]` trait that generates a `UseDelegate`-style provider, which dispatches the component's implementation on one of the trait's generic type parameters using an inner type-level table. - -> **Legacy:** `#[derive_delegate]` and the [`UseDelegate`](../providers/use_delegate.md) provider it generates are a legacy dispatch mechanism. A component no longer needs this attribute to be dispatched on a generic parameter: the `open` statement of [`delegate_components!`](../macros/delegate_components.md) achieves the same per-type dispatch through the [`RedirectLookup`](../providers/redirect_lookup.md) impl that every [`#[cgp_component]`](../macros/cgp_component.md) already generates, wiring the per-value entries directly into the context's own table with better ergonomics and no separate inner table. Prefer `open` for new code, and add `#[derive_delegate]` only when the legacy `UseDelegate` nested-table wiring is specifically wanted. The attribute is retained for compatibility and is expected to be deprecated, and eventually removed, once the namespace-based form is shown to cover every dispatch case. - -## Purpose - -`#[derive_delegate]` solves the problem of choosing a different provider per value of a generic parameter. A component with a generic parameter, such as `CanCalculateArea`, often wants `Rectangle` to be handled by one provider and `Circle` by another. Without help, the author would have to write a dispatcher provider by hand — an impl of the provider trait that looks up the right delegate based on the `Shape` type and forwards every method to it. That impl is mechanical and identical in shape across every component, differing only in which generic parameter is the dispatch key. - -The attribute generates that dispatcher for you. Adding `#[derive_delegate(UseDelegate)]` to the component emits an implementation of the provider trait for [`UseDelegate`](../providers/use_delegate.md) that treats its inner `Components` type as a type-level table keyed on `Shape`, looks up the delegate for each concrete `Shape`, and forwards the call. A context then wires the component to `UseDelegate` and fills `SomeTable` with one provider per shape, getting per-type dispatch without writing the dispatcher. - -A component may need to dispatch on more than one parameter, and `#[derive_delegate]` supports this by accepting several attributes, each naming its own dispatcher type and key. The `UseDelegate` type CGP provides is the default dispatcher, but the same machinery works for any wrapper type, so a component can dispatch on one parameter through `UseDelegate` and on another through a custom dispatcher such as `UseInputDelegate`. Only the parameter named in the dispatcher's angle brackets is used as the lookup key; the others flow through unchanged. - -## Syntax - -`#[derive_delegate]` is applied as an outer attribute on a `#[cgp_component]` trait and takes a wrapper type parameterized by the generic parameter to dispatch on. The single form names one dispatcher and one key: - -```rust -#[cgp_component(AreaCalculator)] -#[derive_delegate(UseDelegate)] -pub trait CanCalculateArea { - fn area(&self, shape: &Shape) -> f64; -} -``` - -`UseDelegate` is the wrapper type that will carry the lookup table, and `Shape` is the trait generic parameter used as the dispatch key. The key may also be a parenthesized tuple of parameters, `UseDelegate<(A, B)>`, when the table should be keyed on more than one parameter at once. - -To dispatch on several parameters independently, repeat the attribute, one per dispatcher. Each names a distinct wrapper type and the single parameter it keys on: - -```rust -use core::marker::PhantomData; - -#[cgp_component(Computer)] -#[derive_delegate(UseDelegate)] -#[derive_delegate(UseInputDelegate)] -pub trait CanCompute { - type Output; - - fn compute(&self, _code: PhantomData, input: Input) -> Self::Output; -} -``` - -Here the default `UseDelegate` dispatches on `Code`, while the custom `UseInputDelegate` dispatches on `Input`. The custom dispatcher is an ordinary struct the user defines — `pub struct UseInputDelegate(pub PhantomData);` — with the same single-type-parameter shape as `UseDelegate`. - -## Expansion - -Each `#[derive_delegate]` attribute emits one additional provider impl alongside everything else `#[cgp_component]` generates. The impl is for the named wrapper applied to a fresh `Components` table type, and it follows the same forwarding shape as a normal provider blanket impl, except that the lookup key is the dispatch parameter rather than the component name. Starting from the single-form example: - -```rust -#[cgp_component(AreaCalculator)] -#[derive_delegate(UseDelegate)] -pub trait CanCalculateArea { - fn area(&self, shape: &Shape) -> f64; -} -``` - -the attribute generates, in addition to the consumer trait, provider trait, and blanket impls, the following dispatcher impl: - -```rust -impl AreaCalculator - for UseDelegate -where - Components: DelegateComponent<(Shape), Delegate = Delegate>, - Delegate: AreaCalculator, -{ - fn area(context: &Context, shape: &Shape) -> f64 { - Delegate::area(context, shape) - } -} -``` - -The dispatch key is wrapped in a tuple, `DelegateComponent<(Shape), ...>`, so that a multi-parameter key composes uniformly. The `Components` type is the inner table: when a context looks up the entry for a concrete `Shape`, `DelegateComponent` yields the `Delegate` provider, and the impl forwards `area` to it. The provider trait's other parameters — here just `Context` — pass through to the delegate unchanged. The generic added for the table is named `__Components__` and the looked-up delegate `__Delegate__` in the real output, alongside the provider trait's own context generic `__Context__`; the shorter names are used here for readability. - -When the component carries supertraits, they ride along into the dispatcher. The provider trait records each supertrait as a `Context: Supertrait` predicate in its `where` clause, and because the dispatcher impl reuses the provider trait's generics, that predicate appears on the generated `UseDelegate` impl as well — so a `#[derive_delegate]` on a trait like `CanRaiseError: HasErrorType` produces a dispatcher whose `where` clause also requires `Context: HasErrorType`. - -When several `#[derive_delegate]` attributes are present, one such impl is generated per attribute, each keyed on its own parameter. The `CanCompute` component above produces one impl for `UseDelegate` keyed on `(Code)` and a second for `UseInputDelegate` keyed on `(Input)`, both forwarding `compute` to the looked-up delegate. The two dispatchers are independent, so a context can pick which parameter to dispatch on, or compose them by nesting one table inside another. - -The inner table is built with [`delegate_components!`](../macros/delegate_components.md), and its nested-table syntax pairs naturally with this provider. A context wires the component to `UseDelegate` and defines `SomeTable`'s entries in the same breath: - -```rust -delegate_components! { - MyApp { - AreaCalculatorComponent: - UseDelegate, - } -} -``` - -The keys in the inner table — `Rectangle`, `Circle` — are the concrete `Shape` types the generated impl looks up, and the values are the providers each one dispatches to. - -## Examples - -A complete dispatching component connects the attribute to a working wiring. The component declares the dispatcher, and two providers implement it for different shapes: - -```rust -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -#[derive_delegate(UseDelegate)] -pub trait CanCalculateArea { - fn area(&self, shape: &Shape) -> f64; -} - -pub struct Rectangle { pub width: f64, pub height: f64 } -pub struct Circle { pub radius: f64 } - -#[cgp_new_provider] -impl AreaCalculator for RectangleArea { - fn area(_context: &Context, shape: &Rectangle) -> f64 { - shape.width * shape.height - } -} - -#[cgp_new_provider] -impl AreaCalculator for CircleArea { - fn area(_context: &Context, shape: &Circle) -> f64 { - core::f64::consts::PI * shape.radius * shape.radius - } -} -``` - -A context wires the component to `UseDelegate` with an inner table mapping each shape to its provider: - -```rust -pub struct MyApp; - -delegate_components! { - MyApp { - AreaCalculatorComponent: - UseDelegate, - } -} -``` - -Now `MyApp` implements `CanCalculateArea` through `RectangleArea` and `CanCalculateArea` through `CircleArea`. The generated `UseDelegate` impl performs the lookup: for a `Rectangle` it reads the `Rectangle` entry from `AreaCalculatorComponents`, finds `RectangleArea`, and forwards `area` to it. - -## Related constructs - -`#[derive_delegate]` is an attribute on [`#[cgp_component]`](../macros/cgp_component.md) and only makes sense for components that carry generic parameters. It generates an impl for the [`UseDelegate`](../providers/use_delegate.md) provider (or a user-defined dispatcher of the same shape), whose role and behavior that document covers in full. The inner lookup table it dispatches through is populated with [`delegate_components!`](../macros/delegate_components.md), whose nested-table syntax is the idiomatic way to define `UseDelegate<...>` wirings in place. - -## Source - -- Parsing: `DeriveDelegateAttribute::parse` in [crates/macros/cgp-macro-core/src/types/attributes/derive_delegate/attribute.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/derive_delegate/attribute.rs), which reads the wrapper identifier and the angle-bracketed key (a single identifier or a parenthesized tuple). -- Dispatcher impl: built by the same file's `to_provider_impl`, which appends `__Components__` and `__Delegate__` generics, emits the `DelegateComponent<(params), Delegate = __Delegate__>` and `__Delegate__: ProviderTrait` bounds, and forwards each method through `trait_items_to_delegated_impl_items`. -- Collection and emission: the attribute is collected in `types/attributes/cgp_component_attributes.rs` and emitted by `to_use_delegate_impls` in [crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/item.rs). -- `UseDelegate` provider (and a worked single-key expansion): [crates/core/cgp-component/src/providers/use_delegate.rs](../../../crates/core/cgp-component/src/providers/use_delegate.rs); the multi-attribute form with a custom `UseInputDelegate` is used in [crates/extra/cgp-handler/src/components/computer.rs](../../../crates/extra/cgp-handler/src/components/computer.rs). -- Implementation document (the dispatcher impl it generates and the index of tests and snapshots): [implementation/asts/attributes/derive_delegate.md](../../implementation/asts/attributes/derive_delegate.md). diff --git a/docs/reference/attributes/extend.md b/docs/reference/attributes/extend.md deleted file mode 100644 index 93bb36aa..00000000 --- a/docs/reference/attributes/extend.md +++ /dev/null @@ -1,127 +0,0 @@ -# `#[extend(...)]` - -`#[extend(...)]` adds the given trait bounds as supertraits of the generated trait, making them a public part of the trait's interface rather than a hidden impl-side dependency. - -## Purpose - -`#[extend(...)]` exists to add supertraits to a CGP trait through an import-like attribute, and in [`#[cgp_fn]`](../macros/cgp_fn.md) it is the only way to do so. A supertrait is a bound that every implementor of a trait must also satisfy, and that every user of the trait may rely on. In `#[cgp_fn]`, the `where` clauses written in the function body are treated as impl-side dependencies and deliberately kept out of the generated trait definition — so there is no place to write a supertrait by hand. `#[extend(...)]` fills that gap: the bounds it lists are promoted onto the trait itself. - -The distinction from [`#[uses]`](uses.md) is the whole point. Both attributes accept the same trait-bound syntax and both feel like imports, but they import into different places. `#[uses(...)]` adds a hidden `Self` bound to the impl only — a private dependency that callers never see. `#[extend(...)]` adds a supertrait to the trait — a public requirement that becomes part of the contract. The natural way to describe the pair is that `#[extend(...)]` is the `pub use` equivalent of `#[uses(...)]`: where `#[uses(...)]` imports a capability for the implementation's own use, `#[extend(...)]` re-exports it as part of what the trait guarantees. - -This framing also explains which supertraits `#[extend(...)]` is for and why it is the preferred way to declare them. `#[extend(...)]` is the tool for a **non-type capability supertrait** — a trait like `HasName` or `CanCalculateArea` that a component depends on but whose associated types it does not name in its own signatures. In [`#[cgp_component]`](../macros/cgp_component.md), prefer `#[extend(HasName)]` over the native `pub trait CanGreet: HasName` form: the native `:` syntax reads as inheritance to programmers coming from object-oriented languages, suggesting an is-a relationship to a parent class that a CGP component does not have, whereas `#[extend(...)]` reads as importing a capability the trait re-exports — which is what a CGP supertrait actually is. When the supertrait is instead an **abstract-type component** whose associated type the signatures reference — such as [`HasErrorType`](../components/has_error_type.md) through its `Error` — prefer [`#[use_type]`](use_type.md), which adds the supertrait *and* rewrites the bare type; `#[use_type]` is the recommended form for abstract-type components. In [`#[cgp_fn]`](../macros/cgp_fn.md), where direct supertrait syntax is unavailable because the body's `where` clauses are impl-side dependencies, `#[extend(...)]` is the only mechanism for a plain capability supertrait. - -## Syntax - -`#[extend(...)]` takes a comma-separated list of trait bounds in the simplified form `TraitIdent`: - -```rust -#[extend(HasScalarType)] -``` - -Each entry names a trait that becomes a supertrait of the generated trait, optionally with generic type arguments. A bare `HasScalarType` becomes a `: HasScalarType` supertrait; a parameterized form carries its arguments through. Multiple bounds may be listed in one attribute or spread across several `#[extend(...)]` attributes, and they accumulate. - -`#[extend(...)]` is accepted in [`#[cgp_fn]`](../macros/cgp_fn.md) and in [`#[cgp_component]`](../macros/cgp_component.md). It is not available in [`#[cgp_impl]`](../macros/cgp_impl.md), because a provider impl has no trait definition of its own to attach supertraits to — the supertraits belong to the component's trait, defined by `#[cgp_component]`. - -## Expansion - -`#[extend(...)]` adds each listed bound as a supertrait of the generated trait, and the same bound also appears in the impl's `where` clause so the implementation may rely on it. The example below uses the abstract-type trait `HasScalarType` to make the two-placement behavior visible in one signature; in production, an abstract-type supertrait like this is better declared with [`#[use_type]`](use_type.md), and `#[extend(...)]` is reserved for a non-type capability supertrait. Starting from a `#[cgp_fn]` definition that depends on an abstract `Scalar` type: - -```rust -pub trait HasScalarType { - type Scalar: Clone + Mul; -} - -#[cgp_fn] -#[extend(HasScalarType)] -fn rectangle_area( - &self, - #[implicit] width: Self::Scalar, - #[implicit] height: Self::Scalar, -) -> Self::Scalar { - width * height -} -``` - -the macro emits a trait carrying `HasScalarType` as a supertrait, and an impl that carries both `Self: HasScalarType` and the `HasField` bounds from the implicit arguments: - -```rust -pub trait RectangleArea: HasScalarType { - fn rectangle_area(&self) -> Self::Scalar; -} - -impl RectangleArea for Context -where - Self: HasScalarType, - Self: HasField - + HasField, -{ - fn rectangle_area(&self) -> Self::Scalar { - let width: Self::Scalar = - self.get_field(PhantomData::).clone(); - let height: Self::Scalar = - self.get_field(PhantomData::).clone(); - - width * height - } -} -``` - -The bound appears in two places for a reason. On the trait it is a supertrait, so `Self::Scalar` resolves and callers know any `RectangleArea` type is also a `HasScalarType`. In the impl `where` clause it lets the implementation actually use the associated type. This double placement is the difference from [`#[uses]`](uses.md), which adds the bound to the impl only. The generated context type is named `__Context__` in the real output; `Context` is used here for readability. - -In [`#[cgp_component]`](../macros/cgp_component.md) the effect is purely on the consumer trait, and it is exactly equivalent to writing the supertrait directly. The definition - -```rust -#[cgp_component(AreaCalculator)] -#[extend(HasScalarType)] -pub trait CanCalculateArea { - fn area(&self) -> Self::Scalar; -} -``` - -is the same as `pub trait CanCalculateArea: HasScalarType`. Although `#[extend(...)]` generates nothing the language cannot already spell here, it is still the preferred way to write the supertrait: it presents the bound as an import rather than as OOP-style inheritance, and it keeps the `use`/`pub use` pairing with `#[uses(...)]` reading consistently across both macros. - -## Examples - -`#[extend(...)]` shines when a `#[cgp_fn]` capability depends on an abstract type that the context must provide. The function below works for any context that defines a `Scalar` type and supplies `width` and `height` fields of that type: - -```rust -use cgp::prelude::*; -use core::ops::Mul; - -pub trait HasScalarType { - type Scalar: Clone + Mul; -} - -#[cgp_fn] -#[extend(HasScalarType)] -pub fn rectangle_area( - &self, - #[implicit] width: Self::Scalar, - #[implicit] height: Self::Scalar, -) -> Self::Scalar { - width * height -} - -#[derive(HasField)] -pub struct Rectangle { - pub width: f64, - pub height: f64, -} - -impl HasScalarType for Rectangle { - type Scalar = f64; -} -``` - -Because `HasScalarType` is a supertrait of `RectangleArea`, the abstract `Self::Scalar` is usable in the signature and body, and `Rectangle` — which implements `HasScalarType` with `Scalar = f64` and derives `HasField` for its two fields — satisfies every bound, gaining `rectangle_area`. - -## Related constructs - -`#[extend(...)]` is the `pub use` counterpart to [`#[uses]`](uses.md): the two share syntax but differ in placement, with `#[extend(...)]` adding public supertraits and `#[uses(...)]` adding hidden impl-side bounds. It is used in [`#[cgp_fn]`](../macros/cgp_fn.md), where it is the only way to declare supertraits, and in [`#[cgp_component]`](../macros/cgp_component.md), where it is the preferred alternative to native supertrait syntax because it reads as an import rather than as OOP-style inheritance. When the supertrait is an abstract-type trait whose associated type is referenced throughout the signature, prefer [`#[use_type]`](use_type.md), which adds the supertrait *and* rewrites bare type names into fully-qualified form. To add `where` clauses (not supertraits) to a `#[cgp_fn]` trait definition, use [`#[extend_where]`](extend_where.md). - -## Source - -- Parsing: `#[extend(...)]` is parsed in [crates/macros/cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs) (the `extend` field of `FunctionAttributes`). -- For `#[cgp_fn]`: the bounds are added to the trait's supertraits and to the impl `where` clause in [crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs). -- For `#[cgp_component]`: the attribute is parsed by `CgpComponentAttributes::parse` and its bounds appended to the consumer trait's supertraits in [crates/macros/cgp-macro-core/src/types/attributes/cgp_component_attributes.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_component_attributes.rs). -- Implementation document (what the attribute injects into each host and the index of tests and snapshots): [implementation/asts/attributes/extend.md](../../implementation/asts/attributes/extend.md). diff --git a/docs/reference/attributes/extend_where.md b/docs/reference/attributes/extend_where.md deleted file mode 100644 index af97b7c9..00000000 --- a/docs/reference/attributes/extend_where.md +++ /dev/null @@ -1,67 +0,0 @@ -# `#[extend_where(...)]` - -`#[extend_where(...)]` adds `where` clauses to the generated trait definition in [`#[cgp_fn]`](../macros/cgp_fn.md), so a bound becomes part of the trait's interface rather than only its implementation. - -## Purpose - -`#[extend_where(...)]` exists to put a `where` clause on the *trait* a `#[cgp_fn]` generates, not just on its impl. By default `#[cgp_fn]` treats every `where` clause written in the function body as an impl-side dependency: the bound goes onto the generated impl and is hidden from the trait definition. That is usually what you want, but sometimes a bound must be visible on the trait itself — for example, a constraint on a generic type parameter that callers of the trait need to know about. `#[extend_where(...)]` is how you promote such a bound onto the trait. - -It complements [`#[extend]`](extend.md). Where `#[extend(...)]` adds *supertrait* bounds (`pub trait Foo: Bar`) to the generated trait, `#[extend_where(...)]` adds *`where`-clause* bounds (`pub trait Foo where T: Bar`) to it. Both make a requirement part of the trait's public interface; they differ only in which syntactic position the bound occupies. - -## Syntax - -`#[extend_where(...)]` takes a comma-separated list of full `where`-clause predicates: - -```rust -#[extend_where(Scalar: Clone)] -``` - -Unlike [`#[uses]`](uses.md), whose entries are bounds attached to `Self` on the generated impl, and [`#[extend]`](extend.md), whose entries become supertraits of the generated trait, `#[extend_where(...)]` accepts arbitrary `where` predicates — a bound on any type, not only `Self` — added verbatim to the generated trait's `where` clause. Each predicate can carry anything a Rust `where` clause allows, including associated-type-equality bounds. - -`#[extend_where(...)]` is supported only in [`#[cgp_fn]`](../macros/cgp_fn.md). It has no meaning in [`#[cgp_impl]`](../macros/cgp_impl.md) or [`#[cgp_component]`](../macros/cgp_component.md), because in those macros the `where` clause you write is already part of the trait definition — there is nothing to promote, so write the bound as a normal `where` clause directly. - -## Expansion - -`#[extend_where(...)]` adds its predicates to the `where` clause of the generated trait, and the same predicates also remain on the impl. Starting from a generic `#[cgp_fn]` definition: - -```rust -#[cgp_fn] -#[extend_where(Scalar: Clone)] -fn rectangle_area( - &self, - #[implicit] width: Scalar, - #[implicit] height: Scalar, -) -> Scalar -where - Scalar: Mul, -{ - width * height -} -``` - -the macro emits a trait whose definition carries the `Scalar: Clone` bound in its own `where` clause: - -```rust -pub trait RectangleArea -where - Scalar: Clone, -{ - fn rectangle_area(&self) -> Scalar; -} -``` - -The `Scalar: Mul` bound, written in the function body, stays as an impl-side dependency on the generated impl and does not appear on the trait. The `Scalar: Clone` bound from `#[extend_where(...)]` is what gets promoted onto the trait definition. - -## Examples - -`#[extend_where(...)]` is the right tool when a generic parameter of a `#[cgp_fn]` trait needs a publicly visible bound. The example above already shows the realistic shape: a `Scalar`-generic area function whose trait advertises `Scalar: Clone` while keeping the multiplication bound private to the impl. The promoted bound means any code naming `RectangleArea` can rely on `Scalar: Clone` without restating it. - -## Related constructs - -`#[extend_where(...)]` is the `where`-clause sibling of [`#[extend]`](extend.md): both promote a requirement onto the generated trait, with `#[extend]` adding supertraits and `#[extend_where(...)]` adding `where` predicates. It is specific to [`#[cgp_fn]`](../macros/cgp_fn.md), since only there are body `where` clauses hidden from the trait. To add hidden impl-side bounds instead of trait-visible ones, use [`#[uses]`](uses.md). - -## Source - -- Parsing: `#[extend_where(...)]` is parsed in [crates/macros/cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs) (the `extend_where` field of `FunctionAttributes`). -- Injection: its predicates are added to both the trait and impl `where` clauses in [crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs). -- Implementation document (what the attribute injects into its host and the index of tests and snapshots): [implementation/asts/attributes/extend_where.md](../../implementation/asts/attributes/extend_where.md). diff --git a/docs/reference/attributes/implicit.md b/docs/reference/attributes/implicit.md deleted file mode 100644 index 35e735c0..00000000 --- a/docs/reference/attributes/implicit.md +++ /dev/null @@ -1,121 +0,0 @@ -# `#[implicit]` - -`#[implicit]` marks a function argument as an implicit dependency: instead of being passed by the caller, the value is read from a same-named field on the context, and the argument disappears from the public signature. - -## Purpose - -`#[implicit]` exists to make field-based dependency injection look like an ordinary function parameter. In plain CGP, a provider that needs a `width` value from its context declares a `HasField` bound in its `where` clause and calls `self.get_field(PhantomData)` inside the body. That works, but it forces the author to understand `HasField`, type-level symbols, and `PhantomData` tags before writing even the simplest provider. `#[implicit]` hides all of that behind a normal-looking parameter. - -The argument named `width: f64` with `#[implicit]` reads as "this function needs a `width` of type `f64`," which is exactly the intuition a Rust programmer already has. The macro then does the mechanical work: it removes the argument from the signature, adds the matching `HasField` bound, and binds a local variable to the field value at the top of the body. The result is code that looks like a function taking arguments but behaves like a provider injecting dependencies from its context. - -This is why `#[implicit]` is the recommended starting point for basic CGP. It lets a newcomer write providers in [`#[cgp_fn]`](../macros/cgp_fn.md) and [`#[cgp_impl]`](../macros/cgp_impl.md) using only familiar function syntax, deferring the `HasField` machinery until they actually need to understand it. - -## Syntax - -`#[implicit]` is written as a bare marker attribute on a typed function argument, and the argument must have a plain identifier name. It takes no arguments in any form — a list or name-value spelling such as `#[implicit(foo)]` or `#[implicit = "foo"]` is rejected with a spanned error rather than silently ignored: - -```rust -fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} -``` - -The argument name doubles as the field name. Here `width` and `height` name both the local variables used in the body and the context fields the values are read from, via `Symbol!("width")` and `Symbol!("height")`. The argument type is the type the body sees, and it determines how the field is accessed (described under Expansion). - -Three rules constrain where `#[implicit]` may appear. The function must take `self` as its first argument, because the field is read from `self`; a function with implicit arguments but no receiver is rejected. The argument pattern must be a bare identifier, not a destructuring or `mut` pattern — to get a mutable local, clone the injected value explicitly inside the body. And a *mutable* implicit argument — one whose type carries a `&mut`, whether the outer reference of a `&mut T`/`&mut [T]` or the inner reference of an `Option<&mut T>` — must be the *only* implicit argument on its function, and requires a `&mut self` receiver: it is read through `get_field_mut`, which borrows the whole context exclusively, so it cannot coexist with any other field read. Immutable implicit arguments carry no such restriction — they are shared borrows and combine freely, in any number, on either a `&self` or a `&mut self` receiver. - -`#[implicit]` is usable wherever CGP rewrites function bodies into providers: inside [`#[cgp_fn]`](../macros/cgp_fn.md) and inside the methods of a [`#[cgp_impl]`](../macros/cgp_impl.md) block. It is not a standalone macro — it is only meaningful as an argument attribute consumed by those macros. - -## Expansion - -`#[implicit]` rewrites each marked argument into a `HasField` bound plus a `let` binding, leaving the rest of the function untouched. Starting from a `#[cgp_fn]` definition: - -```rust -#[cgp_fn] -fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} -``` - -the macro produces a trait whose method takes no extra arguments, and an impl whose `where` clause carries one `HasField` bound per implicit argument: - -```rust -pub trait RectangleArea { - fn rectangle_area(&self) -> f64; -} - -impl RectangleArea for Context -where - Self: HasField - + HasField, -{ - fn rectangle_area(&self) -> f64 { - let width: f64 = self.get_field(PhantomData::).clone(); - let height: f64 = self.get_field(PhantomData::).clone(); - - width * height - } -} -``` - -The two `let` bindings are inserted at the top of the body in argument order, before any of the original statements, so the names are in scope for the rest of the function. The generated context type parameter is literally named `__Context__` in the emitted code; the examples here use `Context` for readability. - -The access expression depends on the argument type, following the same rules as [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md). An owned type — a path type such as `f64` or `String`, or a tuple or array — is read by reference and `.clone()`d, so the body receives an owned value; a plain `&T` is taken by reference with no conversion. Four forms are special: `&str` is backed by a `String` field and read with `.as_str()`; `&[T]` reads any field whose value implements `AsRef<[T]>` and calls `.as_ref()`; `Option<&T>` reads an `Option` field via `.as_ref()`; and `Option<&str>` reads an `Option` field via `.as_deref()`. The mutability of the access follows the *argument's* own type, not the receiver's: an argument carrying a `&mut` reads through `HasFieldMut`/`get_field_mut`, while every immutable argument — a `&[T]` slice included — reads through `HasField`/`get_field` even on a `&mut self` receiver. Each reference form has a mutable mirror: a `&mut [T]` reads a field implementing `AsMut<[T]>` via `.as_mut()`, an `Option<&mut T>` reads an `Option` field via `.as_mut()`, and an `Option<&mut str>` reads an `Option` field via `.as_deref_mut()`. Every mutable form requires a `&mut self` receiver, as described under Syntax. Concretely: - -```rust -#[cgp_fn] -fn greet(&self, #[implicit] name: &str) { - println!("Hello, {}!", name); -} -``` - -expands so that the bound is `HasField` and the binding is `let name: &str = self.get_field(PhantomData::).as_str();`. The field is a `String`, but the argument the body works with is a borrowed `&str`. - -Inside a [`#[cgp_impl]`](../macros/cgp_impl.md) block the rewrite is identical — the same `HasField` bounds are added to the impl's `where` clause and the same `let` bindings are prepended to the method body. For example: - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} -``` - -gains `Self: HasField + HasField` on the impl, with `width` and `height` bound from the context at the top of `area`. - -## Examples - -A complete `#[cgp_fn]` capability with implicit arguments needs only a context that derives [`HasField`](../derives/derive_has_field.md) and contains the named fields: - -```rust -use cgp::prelude::*; - -#[cgp_fn] -pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} - -#[derive(HasField)] -pub struct Rectangle { - pub width: f64, - pub height: f64, -} - -fn print_area(rect: &Rectangle) { - println!("area = {}", rect.rectangle_area()); -} -``` - -`Rectangle` derives `HasField` for `width` and `height`, which satisfies the two bounds the macro added, so `RectangleArea` is implemented for `Rectangle` through the generated blanket impl. The call `rect.rectangle_area()` reads both fields from `rect` and multiplies them — no arguments are passed, because both were declared implicit and are sourced from the context. - -## Related constructs - -`#[implicit]` is most often used inside [`#[cgp_fn]`](../macros/cgp_fn.md), which turns a function into a single-implementation capability, and inside [`#[cgp_impl]`](../macros/cgp_impl.md), which writes a provider for an existing component. It relies on [`#[derive(HasField)]`](../derives/derive_has_field.md) on the context to supply the field accessors that the generated bounds require. Its access rules — `.clone()` for owned values, `.as_str()` for `&str`, and a plain `&T` read by reference with no clone — are shared with [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md), which defines a reusable getter *capability* trait. An implicit argument is the preferred, default way to read any field from a provider's own context; reserve `#[cgp_auto_getter]` for the cases an implicit argument cannot cover — a field that lives on a type other than the provider's context (a getter required as a `where` bound on that type), an accessor that must exist as a named capability other code depends on, or a getter carrying an associated type inferred from the field. To bring in other CGP capabilities alongside implicit arguments, combine `#[implicit]` with [`#[uses]`](uses.md). - -## Source - -- Parsing: implicit-argument parsing lives in [crates/macros/cgp-macro-core/src/functions/implicits/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/implicits/parse.rs), which extracts `#[implicit]`-marked arguments, validates the `self`/`mut` rules, and rejects a malformed (non-bare) `#[implicit]` attribute. -- Per-argument model: [crates/macros/cgp-macro-core/src/types/implicits/](../../../crates/macros/cgp-macro-core/src/types/implicits/) — `arg_field.rs` builds the `HasField` bound and the `let` binding, and `arg_fields.rs` adds the bounds to the impl generics and prepends the bindings to the body. -- Field-type-to-access-mode mapping (`.clone()`, `.as_str()`, `.as_deref()`, and the reference/option/slice cases): [crates/macros/cgp-macro-core/src/functions/field/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/field/parse.rs) and [crates/macros/cgp-macro-core/src/types/getter/get_field_with_mode_expr.rs](../../../crates/macros/cgp-macro-core/src/types/getter/get_field_with_mode_expr.rs). -- Implementation document (how `#[implicit]` arguments are parsed and lowered into `HasField` bounds and `let` bindings, and the index of tests): [implementation/entrypoints/cgp_fn.md](../../implementation/entrypoints/cgp_fn.md). diff --git a/docs/reference/attributes/use_provider.md b/docs/reference/attributes/use_provider.md deleted file mode 100644 index 9999a851..00000000 --- a/docs/reference/attributes/use_provider.md +++ /dev/null @@ -1,133 +0,0 @@ -# `#[use_provider]` - -`#[use_provider]` improves the ergonomics of higher-order providers by writing the inner provider's bound for you, hiding the extra `Self` generic that a provider trait inserts at its first position. - -## Purpose - -`#[use_provider]` exists to keep higher-order providers looking like ordinary providers. A higher-order provider is one that takes another provider as a generic parameter and delegates part of its work to it — for example a `ScaledArea` provider that multiplies whatever an `InnerCalculator` computes. The catch is that provider traits move the original `Self` into an explicit leading `Context` parameter, so the inner provider must be bound as `InnerCalculator: AreaCalculator`, not `InnerCalculator: AreaCalculator`. That stray `` is exactly the detail a reader does not expect, because the consumer trait it mirrors has no such parameter. - -`#[use_provider]` lets the author write the bound without the ``. Annotating an impl with `#[use_provider(InnerCalculator: AreaCalculator)]` adds the `Self` argument back automatically and inserts the completed bound into the impl's `where` clause, so the source reads `InnerCalculator: AreaCalculator` while the generated code carries `InnerCalculator: AreaCalculator`. This preserves the illusion that a provider trait looks the same as the consumer trait it came from, which is why it is the idiomatic way to declare the inner dependency of a higher-order provider. - -The body of such a provider still calls the inner provider as an associated function — `InnerCalculator::area(self)` rather than `self.area()` — because the inner provider is named explicitly rather than routed through the context's own wiring. `#[use_provider]` removes the surprise from the bound; the associated-function call at the use site is written out directly. - -## Syntax - -`#[use_provider]` is an attribute on a `#[cgp_impl]` or `#[cgp_fn]` definition, taking a provider type followed by a colon and the provider trait bounds it should satisfy. The shape is a provider, a colon, and one or more trait bounds joined by `+`: - -```rust -#[use_provider(InnerCalculator: AreaCalculator)] -``` - -`InnerCalculator` is the provider type — usually a generic parameter of the impl — and `AreaCalculator` is the provider trait whose `Self`/context argument the macro fills in. The trait may carry its own further generic arguments after the context slot, and these are preserved in order behind the inserted `Self`. When a provider binds more than one inner provider, prefer supplying all the bounds in a single `#[use_provider]` attribute separated by commas — `#[use_provider(Inner1: TraitA, Inner2: TraitB)]` — since one attribute reads as a single dependency list. Splitting the bounds across stacked attributes behaves identically, but reach for a second attribute only when a real reason calls for it rather than as the default. - -## Expansion - -`#[use_provider]` rewrites nothing in the body; it only completes and inserts the `where`-clause bound. Take this higher-order provider, where `ScaledArea` scales the area produced by an inner calculator: - -```rust -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_impl(new ScaledArea)] -#[use_provider(InnerCalculator: AreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - InnerCalculator::area(self) * scale_factor * scale_factor - } -} -``` - -The attribute takes the bound `InnerCalculator: AreaCalculator`, inserts the context type as the leading generic argument, and pushes the result onto the impl's `where` clause. After this step the impl is equivalent to writing the `` argument by hand: - -```rust -#[cgp_impl(new ScaledArea)] -impl AreaCalculator -where - InnerCalculator: AreaCalculator, -{ - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - InnerCalculator::area(self) * scale_factor * scale_factor - } -} -``` - -The same applies to `#[cgp_fn]`. Here the inner provider is bound and then called as an associated function: - -```rust -#[cgp_fn] -#[use_provider(RectangleAreaCalculator: AreaCalculator)] -fn rectangle_area(&self) -> f64 { - RectangleAreaCalculator::area(self) -} -``` - -This desugars to the blanket impl with the completed bound; note the `` the macro supplied: - -```rust -trait RectangleArea { - fn rectangle_area(&self) -> f64; -} - -impl RectangleArea for Context -where - RectangleAreaCalculator: AreaCalculator, -{ - fn rectangle_area(&self) -> f64 { - RectangleAreaCalculator::area(self) - } -} -``` - -In both cases the body is left untouched, so it must invoke the inner provider directly as an associated function — `RectangleAreaCalculator::area(self)` — passing `self` as the explicit context argument. `#[use_provider]` supplies only the bound; it does not rewrite the call expression. Calling the inner provider as a method (`self.area()`) would instead route through whatever provider the context itself has wired for `AreaCalculator`, which is a different dispatch and usually not what a higher-order provider wants. - -## Examples - -A complete higher-order provider shows the outer form pulling its weight. The base component and a concrete provider come first: - -```rust -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} -``` - -The higher-order `ScaledArea` then wraps any inner calculator and scales its result, declaring the inner dependency with `#[use_provider]`: - -```rust -#[cgp_impl(new ScaledArea)] -#[use_provider(InnerCalculator: AreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - let base_area = InnerCalculator::area(self); - base_area * scale_factor * scale_factor - } -} -``` - -A context can now wire `AreaCalculatorComponent` to `ScaledArea`, and `ScaledArea` will compute the rectangle area through `RectangleArea` and then scale it. The author never wrote `InnerCalculator: AreaCalculator`; `#[use_provider]` supplied the ``. - -## Related constructs - -`#[use_provider]` is written almost exclusively inside [`#[cgp_impl]`](../macros/cgp_impl.md) and [`#[cgp_fn]`](../macros/cgp_fn.md) implementations of components defined with [`#[cgp_component]`](../macros/cgp_component.md), and is the idiomatic tool for the higher-order provider pattern those macros support. It is the provider-bound counterpart to [`#[uses]`](uses.md), which imports consumer-trait dependencies on `Self`; where `#[uses]` adds a bound on the context, `#[use_provider]` adds a bound on a separate provider type and fills in that type's context argument. For dispatching to different providers based on a generic type rather than naming one statically, see [`UseDelegate`](../providers/use_delegate.md) and [`#[derive_delegate]`](derive_delegate.md). - -## Known issues - -`#[use_provider]` only completes and inserts a `where`-clause bound; there is no call-site form that rewrites a method call into a provider dispatch. The attribute's parser requires the `Provider: Trait` shape — a provider, a colon, and the trait bounds — so a bare `#[use_provider(InnerCalculator)]` applied to an expression is not accepted, and no pass rewrites `receiver.method(args)` into `Provider::method(receiver, args)`. A body that delegates to a named inner provider must therefore spell the associated-function call out itself, as `InnerCalculator::area(self)`. - -## Source - -- Parsing: the outer form is parsed by `UseProviderAttribute` in [crates/macros/cgp-macro-core/src/types/attributes/use_provider/attribute.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/use_provider/attribute.rs); its `to_type_param_bounds` inserts the context type at index 0 of the trait's generic arguments, and `to_provider_bounds` builds the `where` predicate. -- Bound insertion: the bounds are appended to the impl by `add_type_param_bounds` in `attributes.rs`. -- Collection and application: the attribute is collected for `#[cgp_impl]` in `types/attributes/cgp_impl_attributes.rs` and for `#[cgp_fn]` in `types/attributes/function.rs`, and applied in `types/cgp_impl/item.rs` and `types/cgp_fn/preprocessed.rs`. -- Implementation document (the internal AST type, the bound completion, and the index of tests and snapshots): [implementation/asts/attributes/use_provider.md](../../implementation/asts/attributes/use_provider.md). diff --git a/docs/reference/attributes/use_type.md b/docs/reference/attributes/use_type.md deleted file mode 100644 index 3fe68f82..00000000 --- a/docs/reference/attributes/use_type.md +++ /dev/null @@ -1,214 +0,0 @@ -# `#[use_type]` - -`#[use_type]` imports an abstract associated type into a `#[cgp_fn]`, `#[cgp_impl]`, or `#[cgp_component]` definition and rewrites every bare mention of that type into the fully-qualified `::AssocType` form, adding the trait as a supertrait or bound at the same time. - -## Purpose - -`#[use_type]` removes the boilerplate of referring to an abstract type that lives on another CGP trait. A CGP trait often needs a type that is defined elsewhere — a `Scalar` from `HasScalarType`, an `Error` from `HasErrorType` — and Rust requires every reference to that type to be written in fully-qualified form, `::Scalar`, because a bare `Scalar` is not a type the compiler knows about. Writing that prefix on every occurrence, in the return type, in each implicit argument, and in the body, is verbose and easy to get wrong. - -The attribute lets you write the bare identifier `Scalar` everywhere and have the macro expand it for you. You declare the type once in the attribute — `#[use_type(HasScalarType.Scalar)]` — and the macro replaces each standalone `Scalar` type with `::Scalar`, while also adding `HasScalarType` as a supertrait of the generated trait (for `#[cgp_component]`) or as a `where`-clause bound on the impl (for `#[cgp_impl]` and `#[cgp_fn]`). The bare identifier reads like a normal generic, but resolves to the qualified associated type. - -Beyond saving keystrokes, the fully-qualified rewrite removes ambiguity that the bare form cannot express. Because the macro always emits the `::Type` path, nested associated types compose without the author ever spelling out the path, foreign abstract types can be pulled from a type parameter rather than `Self`, and type-equality constraints between two imported types can be stated declaratively. These capabilities are why the `/cgp` skill recommends `#[use_type]` as the default way to import abstract types in all three macros. - -## Syntax - -`#[use_type]` is applied as an outer attribute alongside the `#[cgp_fn]`, `#[cgp_impl]`, or `#[cgp_component]` attribute, and its argument names a trait and one or more of its associated types. A `.` separates the trait from the associated type — not `::` — which is what lets the trait itself be a full path or carry generic arguments without the parser confusing a path segment for the associated type. The simplest form imports a single type from a trait: - -```rust -#[use_type(HasScalarType.Scalar)] -``` - -The part before the `.` is the trait and the identifier after it is the associated type to import. The rewrite target — the type the bare identifier expands into — defaults to `Self`, so the example above rewrites `Scalar` to `::Scalar`. - -Because the `.` is the only separator the macro looks for, the trait may be written as a full path or with generic arguments, both using ordinary `::`. `#[use_type(errors::HasErrorType.Error)]` imports from a trait named by path without bringing it into scope, and `#[use_type(HasFooType.Foo)]` imports the associated type of a specific generic instantiation, rewriting `Foo` to `>::Foo`. This is what makes it possible to import the same associated type from two instantiations under different aliases, as in `#[use_type(HasFooType.{Foo as FooX}, HasFooType.{Foo as FooY})]`. - -A trailing `in Context` clause changes the rewrite target from `Self` to a named type, which is how foreign abstract types are imported. The form `#[use_type(HasScalarType.Scalar in Types)]` treats `Types` as the context type and rewrites `Scalar` to `::Scalar`. `Types` is typically a generic parameter of the function or impl rather than `Self`, which lets a trait pull an abstract type from a parameter instead of from the implementing context. The `in` keyword is reserved in Rust, so it can never be confused with a trait, type, or associated-type name and reads as a clean delimiter after the associated-type list; the clause is consistent with the `in` used elsewhere in CGP wiring, such as `#[prefix(@Path in Namespace)]`. - -Several types from the same trait can be imported in one attribute using a braced list, and each entry may be renamed with `as` or constrained with `=`. The braced form `#[use_type(HasFooType.{Foo, Bar as Baz})]` imports `Foo` under its own name and `Bar` under the local alias `Baz`. The equality form `#[use_type(HasScalarType.{Scalar = f64})]` imports `Scalar` and additionally constrains it, emitting `Self: HasScalarType` in the `where` clause. A braced list may itself carry a foreign context — `#[use_type(HasFooType.{Foo, Bar} in Ctx)]` projects every imported type in the group against `Ctx`, since the `in` clause scopes over the whole spec, not a single entry. - -When a definition imports types from several traits at once, prefer combining them into a single `#[use_type]` attribute by separating the trait paths with commas — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — since one attribute reads as a single import list. Stacking multiple `#[use_type]` attributes is also accepted and behaves identically, as in `#[use_type(HasBarType.{Bar as Baz = Foo}, HasFooType.Foo)]` written across two lines, but reach for a second attribute only when a real reason calls for it rather than as the default. - -Two restrictions guard against ambiguous imports. No two imports may resolve to the same bare identifier or alias — whether they appear in different specs or in the same braced list, and on `#[cgp_component]` as well as on `#[cgp_fn]` and `#[cgp_impl]` — because the substitution could then only pick one and would silently drop the rest; a collision is a compile error. And the `= ...` type-equality form is rejected on `#[cgp_component]` specifically, because a trait definition cannot carry the impl-side equality constraint the equality form produces; equality constraints belong on `#[cgp_fn]` and `#[cgp_impl]`, where they become `where` bounds. - -## Syntax Grammar - -The grammar below covers the tokens inside `#[use_type(...)]` — the comma-separated list of import specs, not the surrounding attribute delimiters. - -```ebnf -UseTypeArgs -> UseTypeSpec (`,` UseTypeSpec)* `,`? - -UseTypeSpec -> TraitPath `.` TypeItems (`in` ContextPath)? - -ContextPath -> TypePath -TraitPath -> TypePath - -TypeItems -> UseTypeIdent - | `{` UseTypeIdent (`,` UseTypeIdent)* `,`? `}` - -UseTypeIdent -> IDENTIFIER (`as` IDENTIFIER)? (`=` Type)? -``` - -`ContextPath` and `TraitPath` are ordinary Rust `TypePath`s (a path whose final segment may carry angle-bracketed generic arguments); their `::` segments belong to the path, while the `.` after the trait starts the associated-type list. An omitted `in ContextPath` clause defaults the rewrite target to `Self`; because `in` is a reserved keyword it can never appear inside `TypeItems`, so it marks the context clause unambiguously. In each `UseTypeIdent`, the leading `IDENTIFIER` is the associated type's own name, an `as` clause gives it a local alias to write in the signature, and an `= Type` clause pins it with an equality bound (accepted on `#[cgp_fn]` and `#[cgp_impl]`, rejected on `#[cgp_component]`). - -## Expansion - -`#[use_type]` runs before the rest of the macro in three steps: it first *grounds* each import's context (resolving an `in Context` that names another import into a fully-qualified path), then substitutes every matching bare type identifier with the qualified associated type in one pass, and finally adds the trait as a supertrait or bound. Consider this `#[cgp_fn]` using the single-import form: - -```rust -pub trait HasScalarType { - type Scalar: Clone + Mul; -} - -#[cgp_fn] -#[use_type(HasScalarType.Scalar)] -fn rectangle_area( - &self, - #[implicit] width: Scalar, - #[implicit] height: Scalar, -) -> Scalar { - width * height -} -``` - -The macro first rewrites every standalone `Scalar` to `::Scalar` and appends `HasScalarType` to the bounds, then desugars the resulting `#[cgp_fn]` as usual. The effective expansion is: - -```rust -pub trait RectangleArea: HasScalarType { - fn rectangle_area(&self) -> ::Scalar; -} - -impl RectangleArea for Context -where - Self: HasField::Scalar> - + HasField::Scalar>, - Self: HasScalarType, -{ - fn rectangle_area(&self) -> ::Scalar { - let width: ::Scalar = - self.get_field(PhantomData::).clone(); - let height: ::Scalar = - self.get_field(PhantomData::).clone(); - width * height - } -} -``` - -The substitution is purely textual at the type level: it matches single-segment type paths with no arguments whose identifier equals the imported name (or its alias), and replaces them with `::Scalar`. A bare `Scalar` anywhere — return type, implicit-argument annotation, or a `let` binding inside the body — is rewritten the same way, which is what makes nested uses work without the author writing any path. - -Because the rewrite fires only on the bare identifier of an *imported* type, a construct's own **local associated types must always stay qualified as `Self::Assoc`** and are left untouched. A `#[cgp_component]` trait or a `#[cgp_impl]` provider that declares its own `type Output` refers to it as `Self::Output`, never as a bare `Output`, precisely because `Output` is the construct's own type rather than one imported from another trait — `#[use_type]` neither imports it nor rewrites it, and it should not be listed in a `#[use_type]` attribute. This is why a mixed signature such as `Result` is correct and idiomatic: the local `Self::Output` stays qualified while the imported foreign type `Error` (from `#[use_type(HasErrorType.Error)]`) is written bare. Attempting to write the local type bare would leave a `Output` identifier that resolves to nothing, since the substitution pass has no entry for it. - -For `#[cgp_component]`, the trait is added as a supertrait rather than a `where` bound, and the rewrite touches the trait's own signatures. Starting from: - -```rust -#[cgp_component(AreaCalculator)] -#[use_type(HasScalarType.Scalar)] -pub trait CanCalculateArea { - fn area(&self) -> Scalar; -} -``` - -the `#[use_type]` phase rewrites the trait into the following before `#[cgp_component]` proceeds: - -```rust -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea: HasScalarType { - fn area(&self) -> ::Scalar; -} -``` - -The supertrait is added only when the rewrite target is `Self`. With the foreign-type `in Context` form the target is a named type, so the bound cannot be a supertrait of `Self`; instead the macro adds a plain `Context: Trait` predicate wherever the substituted `::Assoc` paths appear. On `#[cgp_fn]` and `#[cgp_impl]` that predicate lands in the impl's `where` clause, and on `#[cgp_component]` and `#[cgp_fn]` it is *also* added to the generated trait's own `where` clause — because the trait's signatures now name `::Assoc` and would not be well-formed without it. This means a component using the `in` form no longer has to declare the parameter's bound by hand; writing `pub trait CanCalculateArea` is enough, and `Types: HasScalarType` is supplied for you. (The type-equality `= T` pin, by contrast, stays impl-side and is never added to the trait.) This `#[cgp_fn]` imports `Scalar` from a generic parameter `Types`: - -```rust -#[cgp_fn] -#[use_type(HasScalarType.Scalar in Types)] -pub fn rectangle_area( - &self, - #[implicit] width: Scalar, - #[implicit] height: Scalar, -) -> Scalar -where - Scalar: Mul + Copy, -{ - let res: Scalar = width * height; - res -} -``` - -Every `Scalar`, including the ones in the explicit `where` clause, expands to `::Scalar`, and the bound `Types: HasScalarType` is added to *both* the generated trait's `where` clause and the impl's — so the plain, unbounded `` the author wrote is enough. It is a `where` bound rather than a supertrait because the target is a named type, not `Self`: - -```rust -pub trait RectangleArea -where - Types: HasScalarType, -{ - fn rectangle_area(&self) -> ::Scalar; -} - -impl RectangleArea for Context -where - ::Scalar: - Mul::Scalar> + Copy, - Self: HasField::Scalar> - + HasField::Scalar>, - Types: HasScalarType, -{ - fn rectangle_area(&self) -> ::Scalar { - let width: ::Scalar = - self.get_field(PhantomData::).clone(); - let height: ::Scalar = - self.get_field(PhantomData::).clone(); - let res: ::Scalar = width * height; - res - } -} -``` - -The type-equality form adds a constrained bound on top of the substitution. Writing `#[use_type(HasScalarType.{Scalar = f64})]` substitutes `Scalar` to `::Scalar` exactly as before, but emits `Self: HasScalarType` in the `where` clause in place of the plain `Self: HasScalarType`, pinning the abstract type to `f64`. When one import's equality target names another import's alias — as in `#[use_type(HasBarType.{Bar as Baz = Foo}, HasFooType.Foo)]` — the macro resolves the target across specs and emits `Self: HasBarType::Foo>`, tying the two abstract types together. This cross-spec resolution relies on aliases being unique, which the duplicate check described in the Syntax section guarantees. - -Because grounding resolves every context against all specs at once and iterates to a fixpoint, the *order* in which nested imports are written does not matter: `#[use_type(HasC.C in B, HasB.B in A, HasA.A)]` grounds identically to the front-to-back `#[use_type(HasA.A, HasB.B in A, HasC.C in B)]`. The one arrangement without a valid order is a cycle — two `in Context` clauses that resolve through each other, as in `#[use_type(HasA.A in B, HasB.B in A)]`. Grounding stops rather than loops on a cycle, so the aliases are never resolved and the compiler reports `E0425` "cannot find type" at the offending `in` alias; correct the imports so the contexts form an acyclic chain. - -## Examples - -A realistic use threads one abstract `Scalar` type through a component and a provider, with neither writing `Self::` by hand. The component and the type trait come first: - -```rust -use cgp::prelude::*; -use core::ops::Mul; - -#[cgp_type] -pub trait HasScalarType { - type Scalar: Clone + Mul; -} - -#[cgp_component(AreaCalculator)] -#[use_type(HasScalarType.Scalar)] -pub trait CanCalculateArea { - fn area(&self) -> Scalar; -} -``` - -`CanCalculateArea` ends up with `HasScalarType` as a supertrait and `area` returning `::Scalar`. A provider for it imports the same type and writes its body in terms of the bare name: - -```rust -#[cgp_impl(new RectangleArea)] -#[use_type(HasScalarType.Scalar)] -impl AreaCalculator { - fn area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar { - width * height - } -} -``` - -The provider's `#[use_type]` adds `Self: HasScalarType` to its `where` clause and rewrites each `Scalar` to the qualified path, so the implicit `width` and `height` fields and the return value all agree on the context's chosen scalar type. A concrete context then wires `HasScalarType` to a concrete type with `UseType` and supplies the fields, and `area()` works without any reference to associated-type syntax in the user's own code. - -## Related constructs - -`#[use_type]` is most often paired with [`#[cgp_type]`](../macros/cgp_type.md), which defines the abstract type trait it imports, and with the [`UseType` provider](../providers/use_type.md), which a context uses to bind that abstract type to a concrete one. It applies to all three implementation macros — [`#[cgp_fn]`](../macros/cgp_fn.md), [`#[cgp_impl]`](../macros/cgp_impl.md), and [`#[cgp_component]`](../macros/cgp_component.md) — adjusting whether it emits a supertrait or a `where` bound based on which it annotates. It overlaps in role with [`#[extend]`](extend.md), which adds a supertrait bound without rewriting type identifiers; `#[use_type]` is preferred when the imported type is actually mentioned in signatures, since it also performs the substitution. The abstract type itself is read through [`HasType`](../components/has_type.md) at the provider level. - -## Source - -- Parsing: the attribute is parsed by `UseTypeAttribute` in [crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs), which reads the trait as a `PathWithTypeArgs`, the associated-type list after the `.`, and an optional `in Context` (also a `PathWithTypeArgs`). Per-type entries (`as` alias and `=` equality) are in `ident.rs`. -- Three-step transform (ground contexts, substitute, add bounds): lives in `attributes.rs`. `grounded_specs` resolves each `in Context` that names another import into a fully-qualified path; `transform_item_trait` then substitutes and adds bounds to a trait (a supertrait for a `Self` import, a `where` bound for a foreign `in` import — this is `#[cgp_component]` and `#[cgp_fn]`), and `transform_item_impl` does the same for an impl's `where` clause. Both call `forbid_duplicate_aliases` first to reject a shared identifier or alias, and the impl-side type-equality predicates are derived in `type_predicates.rs`. -- Identifier substitution: the `SubstituteAbstractTypes` `VisitMut` pass in [crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs](../../../crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs), which holds every grounded spec at once and rewrites single-segment, argument-free type paths in a single traversal. -- `= ...` rejection for component traits: enforced in `types/attributes/cgp_component_attributes.rs`. -- Implementation document (the internal AST types, the two-phase transform, and the index of tests and snapshots): [implementation/asts/attributes/use_type.md](../../implementation/asts/attributes/use_type.md). diff --git a/docs/reference/attributes/uses.md b/docs/reference/attributes/uses.md deleted file mode 100644 index e102164e..00000000 --- a/docs/reference/attributes/uses.md +++ /dev/null @@ -1,111 +0,0 @@ -# `#[uses(...)]` - -`#[uses(...)]` adds simple `Self: Trait<...>` bounds to a provider's `where` clause, written to read like a `use` import of the CGP capabilities the body depends on. - -## Purpose - -`#[uses(...)]` exists to make impl-side dependencies look like imports rather than trait bounds. A provider often calls capabilities defined elsewhere — another [`#[cgp_fn]`](../macros/cgp_fn.md) trait, or a [`#[cgp_component]`](../macros/cgp_component.md) consumer trait — and to do so it must require the context to implement them. Expressed in raw Rust, that is a `where Self: SomeTrait` clause, which is unfamiliar territory for many programmers: writing a bound on `Self` is uncommon in everyday Rust, and it reads as machinery rather than intent. - -`#[uses(RectangleArea)]` instead reads as "this function uses the `RectangleArea` capability," which mirrors the mental model of a `use` statement bringing a name into scope. The attribute lists the capabilities the body relies on; the macro turns each into the corresponding `Self` bound on the generated impl. The body can then call those methods directly on `self`, exactly as if they had been imported. - -This framing is the reason `#[uses(...)]` is recommended over hand-written `Self` bounds in basic CGP. It keeps the dependency declaration close in spirit to the `use` statements a reader already understands, and it keeps the code focused on *what* the provider needs rather than *how* the bound is spelled. - -## Syntax - -`#[uses(...)]` takes a comma-separated list of trait bounds, each of which becomes a `Self:` predicate on the generated impl: - -```rust -#[uses(RectangleArea, CanCalculateArea)] -``` - -Each entry is the name of a capability, optionally with generic type arguments. A bare `RectangleArea` becomes `Self: RectangleArea`; a parameterized `CanCompute` becomes `Self: CanCompute`. When a provider depends on several capabilities, prefer listing them all in a single `#[uses(...)]` attribute — `#[uses(RectangleArea, CanCalculateArea)]` — since one combined import reads as a single dependency list. The entries may also be split across multiple `#[uses(...)]` attributes on the same item, and they accumulate, but stack a second attribute only when a genuine reason calls for it rather than as the default. - -The idiomatic entry is the simple `Trait` form, since the attribute is meant to read like an import. An entry may nonetheless be any bound a Rust `where` clause accepts, including an associated-type-equality binding (`HasErrorType`), a higher-ranked bound, or a lifetime bound; each lands on the impl's `where` clause verbatim. Use that generality sparingly. To pin an abstract type to a concrete one, prefer the [`#[use_type]`](use_type.md) equality form `#[use_type(HasErrorType.{Error = anyhow::Error})]`, which adds the bound and also rewrites bare mentions of the type; and to place a bound on the generated *trait* rather than only its impl, use [`#[extend_where]`](extend_where.md) (on `#[cgp_fn]`). - -`#[uses(...)]` is accepted in both [`#[cgp_fn]`](../macros/cgp_fn.md) and [`#[cgp_impl]`](../macros/cgp_impl.md). In either case it imports capabilities into the provider being defined, and those capabilities may themselves be defined with either [`#[cgp_fn]`](../macros/cgp_fn.md) or [`#[cgp_component]`](../macros/cgp_component.md) — the attribute does not care how the imported capability was produced, only that it is a trait the context can implement. - -## Expansion - -`#[uses(...)]` adds one `Self`-anchored predicate to the generated impl's `where` clause for the listed bounds, and changes nothing about the trait definition. Starting from two `#[cgp_fn]` capabilities where the second depends on the first: - -```rust -#[cgp_fn] -fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} - -#[cgp_fn] -#[uses(RectangleArea)] -fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { - self.rectangle_area() * scale_factor * scale_factor -} -``` - -the second definition expands so that the trait is unchanged but the impl gains a `Self: RectangleArea` predicate, sitting alongside the `HasField` bound that the implicit `scale_factor` argument introduces: - -```rust -pub trait ScaledRectangleArea { - fn scaled_rectangle_area(&self) -> f64; -} - -impl ScaledRectangleArea for Context -where - Self: RectangleArea, - Self: HasField, -{ - fn scaled_rectangle_area(&self) -> f64 { - let scale_factor: f64 = - self.get_field(PhantomData::).clone(); - - self.rectangle_area() * scale_factor * scale_factor - } -} -``` - -The imported bound lands on the impl only, never on the trait — it is an impl-side dependency, hidden from anyone who merely uses `ScaledRectangleArea`. Writing `#[uses(RectangleArea)]` is therefore exactly equivalent to writing `where Self: RectangleArea` in the function body; the two desugar identically. The generated context type is named `__Context__` in the real output, shown as `Context` here for readability. - -Inside [`#[cgp_impl]`](../macros/cgp_impl.md) the behavior is the same. The listed bounds are appended to the impl's `where` clause as `Self` predicates, so a provider can import a `#[cgp_fn]` capability and call it directly: - -```rust -#[cgp_impl(new RectangleAreaCalculator)] -#[uses(RectangleArea)] -impl AreaCalculator { - fn area(&self) -> f64 { - self.rectangle_area() - } -} -``` - -This adds `Self: RectangleArea` to the `RectangleAreaCalculator` impl, letting `area` call `self.rectangle_area()`. The component remains usable by any context that implements `RectangleArea`, regardless of which provider supplies it. - -## Examples - -`#[uses(...)]` is the natural way to build a capability on top of a CGP component. Given an `AreaCalculator` component, a scaled-area function can import it and use it without knowing which provider is wired: - -```rust -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_fn] -#[uses(CanCalculateArea)] -pub fn scaled_area(&self, #[implicit] scale_factor: f64) -> f64 { - self.area() * scale_factor * scale_factor -} -``` - -The `#[uses(CanCalculateArea)]` attribute adds `Self: CanCalculateArea` to the generated `ScaledArea` impl, so the body may call `self.area()`. Any context that implements `CanCalculateArea` — through whatever `AreaCalculator` provider it has wired — automatically gains `scaled_area`, because the dependency is on the consumer trait rather than on a specific provider. - -## Related constructs - -`#[uses(...)]` is the impl-side counterpart to [`#[extend]`](extend.md): both add trait bounds to a generated construct, but `#[uses(...)]` adds hidden impl-side `where` bounds while `#[extend]` adds public supertraits — the `use` versus `pub use` distinction. It is used inside [`#[cgp_fn]`](../macros/cgp_fn.md) and [`#[cgp_impl]`](../macros/cgp_impl.md), and the capabilities it imports are typically defined with [`#[cgp_fn]`](../macros/cgp_fn.md) or [`#[cgp_component]`](../macros/cgp_component.md). To import an abstract associated type rather than a method capability, use [`#[use_type]`](use_type.md); to bring in a context field as a function argument, use [`#[implicit]`](implicit.md). To pin an abstract type, prefer the [`#[use_type]`](use_type.md) equality form over spelling the equality in `#[uses(...)]`; to make a bound part of the generated trait rather than only its impl, use [`#[extend_where]`](extend_where.md). - -## Source - -- Parsing: `#[uses(...)]` parsing lives in [crates/macros/cgp-macro-core/src/types/attributes/uses.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/uses.rs) (the `UsesAttributes` type and its `to_type_param_bounds`). -- Dispatch: the attribute dispatch is in [crates/macros/cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs) for `#[cgp_fn]` and [crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs) for `#[cgp_impl]`. -- Injection: the bounds are added to the impl `where` clause in [crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs). -- Implementation document (the internal AST type, what the attribute injects into each host, and the index of tests and snapshots): [implementation/asts/attributes/uses.md](../../implementation/asts/attributes/uses.md). diff --git a/docs/reference/cargo-cgp.md b/docs/reference/cargo-cgp.md deleted file mode 100644 index 11682756..00000000 --- a/docs/reference/cargo-cgp.md +++ /dev/null @@ -1,151 +0,0 @@ -# cargo-cgp — the CGP error toolchain - -`cargo-cgp` is CGP's first-class toolchain: a cargo subcommand that stands in for `cargo check` and rewrites CGP's compiler errors into a compact, root-cause-first form, and that also expands a target's macros with CGP's type-level constructs made legible. It is the **recommended way to build and check CGP code and to read a CGP compile error**, and an agent working in this repository should reach for it before plain `cargo check` when diagnosing a wiring failure. This document is enough to install and use it without leaving `cgp`; the tool itself lives in a separate repository, [github.com/contextgeneric/cargo-cgp](https://github.com/contextgeneric/cargo-cgp), and its own docs are the exhaustive reference. - -This is a *tooling* reference, not a CGP construct — it is the one document under `reference/` that describes an external tool rather than a macro, trait, or provider. The [error catalog](../errors/README.md) is its companion: the catalog documents each error *class* and how `cargo-cgp` presents it, while this document covers running the tool — both its `check` and its [`expand`](#expanding-the-generated-code) command. - -## Why it exists - -A CGP macro expands to ordinary Rust, so the compiler type-checks the *generated* code, and a small wiring mistake surfaces as a wall of errors naming types the programmer never wrote — with the real cause buried under machinery like `IsProviderFor` and `CanUseComponent`, encoded as a nested `Symbol<…>` spine, or hidden from the output entirely (the [hidden-versus-surfaced axis](../errors/README.md#the-central-axis-hidden-versus-surfaced) the catalog is built around). `cargo-cgp` reads those diagnostics inside the compiler and re-presents them: it names the real cause, renders the dependency chain that leads to it as a `cargo tree`-style tree, and tags each rewritten message with a `[CGP-Exxx]` code — much as Clippy layers its own analysis on top of `rustc`. - -Two mechanisms do the work, and both matter when reading its output. It compiles through a `rustc` wrapper that turns on the **next-generation trait solver** (`-Znext-solver`), which surfaces dependency errors the default solver hides — this alone recovers the root cause of the [hidden unsatisfied-dependency](../errors/hidden/unsatisfied-dependency.md) class that plain `cargo check` suppresses. On top of that it **rewrites the classes it recognizes** into the coded, root-cause-first form. Classes it does not yet rewrite pass through as the compiler wrote them. - -## Installing - -`cargo-cgp` is two binaries — the `cargo-cgp` front-end and a `cargo-cgp-driver` that links the compiler internals — and it requires an exact Rust nightly (the one the driver is built against). That nightly is installed by `cargo cgp setup` (or built by the Nix flake), and cargo-cgp forces it only for its own check, so **your `cgp` project keeps its own toolchain** — the pinned stable in [`rust-toolchain.toml`](../../rust-toolchain.toml) — for its ordinary builds. - -With **cargo** (rustup present): - -```sh -cargo install cargo-cgp # the front-end; builds on any toolchain -cargo cgp setup # provisions the pinned nightly + driver, in lockstep -``` - -With **Nix** (pinning the pre-release tag for a reproducible install): - -```sh -nix profile install github:contextgeneric/cargo-cgp/v0.1.0-alpha -``` - -The full matrix — installing from source, updating, and uninstalling — is in cargo-cgp's [installation guide](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/reference/installation.md). - -## Running it without installing - -To check a project through the tool without installing anything — the quickest way to try it on this repository's examples or a scratch reproduction — run the flake's default app from the project directory, pinning the tag: - -```sh -cd /path/to/a/cgp/project -nix run github:contextgeneric/cargo-cgp/v0.1.0-alpha -- check -``` - -Everything after `--` is forwarded to `cargo check`. This needs no rustup and leaves the project's toolchain and `target/` untouched. - -## Using it - -Run it wherever you would run `cargo check`: - -```sh -cargo cgp check # like `cargo check`, with CGP errors reshaped -cargo cgp check --workspace # every argument after `check` is forwarded to `cargo check` -``` - -`check` is the command you reach for day to day; its companion [`expand`](#expanding-the-generated-code) shows the Rust your macros generate. Neither changes how you build: both re-compile your code under the pinned nightly purely to show you something, and there is no `cargo cgp build`, `run`, or `test`; run those with plain cargo (besides these two, only `setup` and `update` exist, and they merely provision the tool). **cargo-cgp is optional, and its advantage is readable errors — and readable expansions — during development.** CGP itself is an ordinary library that compiles on any stable Rust ≥ 1.89, so `cargo check`, `cargo build`, `cargo run`, and `cargo test` all work on a CGP project unchanged. Reach for `cargo cgp check` when you hit — or expect — a wiring error and want it readable; use plain `cargo check` when you do not; and always build, run, and test with ordinary cargo. - -Read its output as ordinary rustc/cargo diagnostics with the recognized CGP errors rewritten. When the tool rewrites an error into a known class it stamps a short code in brackets and leads with the cause: - -```text -error[E0277]: [CGP-E001] the consumer trait `CanCalculateArea` is not implemented for context `Rectangle` - = note: root cause: [CGP-E106] missing field `height` on `Rectangle` - this is required through the dependency chain: - [CGP-E101] consumer trait impl `CanCalculateArea` for context `Rectangle` - └─ [CGP-E102] provider trait impl `AreaCalculator` with context `Rectangle` for provider `RectangleArea` - └─ [CGP-E106] missing field `height` on `Rectangle` -``` - -The diagnostic keeps its own Rust code (`E0277`), so `rustc --explain` still works; the `[CGP-Exxx]` tag rides inside the message. The codes are catalogued in cargo-cgp's [error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md), and each class in this repository's [error catalog](../errors/README.md) records which code it maps to under its *How cargo-cgp presents it* section. - -To use it as **Rust Analyzer's** on-save check backend so the reshaped errors appear inline, wire it through `check.overrideCommand` (it is a two-word command and must emit JSON): - -```jsonc -"rust-analyzer.check.overrideCommand": [ - "cargo", "cgp", "check", "--workspace", "--all-targets", "--message-format=json" -] -``` - -Never apply this to a user's Rust Analyzer configuration on your own initiative — present it as an option and edit their editor settings only when they explicitly ask. - -## Expanding the generated code - -`cargo cgp expand` prints the crate as the compiler sees it after macro expansion, which answers the question a CGP error cannot: *what did that macro actually generate?* Reach for it when a wiring failure stops making sense from the message alone — the failure is always "the emitted impls do not resolve," and you cannot reason about which impl is wrong until you read it — or simply to confirm what a construct produces before trusting your memory of it. - -What makes it worth using over plain `cargo expand` is that CGP's type-level constructs come back **resugared**: a field tag reads `Symbol!("width")` rather than the raw `Symbol<5, Chars<'w', …>>` spine the compiler prints, a handler pipeline reads `Product![StepOne, StepTwo]`, and a namespace key reads `Path!(@app.GreeterComponent)`. Everything else is a full expansion — `#[derive(Debug)]` and `println!` appear in their generated form too — so what you get is the whole program with the CGP parts legible. Expanding the [area-calculation](../examples/area-calculation.md) example's `rectangle_area` function shows the shape: - -```sh -cargo cgp expand --lib --item RectangleArea -``` - -```rust -pub trait RectangleArea { - fn rectangle_area(&self) -> f64; -} -impl<__Context__> RectangleArea for __Context__ -where - Self: HasField - + HasField, -{ - fn rectangle_area(&self) -> f64 { - let width: f64 = self - .get_field(::core::marker::PhantomData::) - .clone(); - let height: f64 = self - .get_field(::core::marker::PhantomData::) - .clone(); - width * height - } -} -``` - -Reading that answers two questions at once: what the trait `#[cgp_fn]` generated looks like, and what the `#[implicit]` arguments turned into — a `HasField` bound per argument and a `get_field` call that clones the value. - -**It expands exactly one target**, so a package with both a library and a binary needs `--lib` or `--bin NAME`; without one, cargo declines with *"extra arguments to `rustc` can only be passed to one target"*. Everything else is forwarded to `cargo rustc`, so `-p`, `--features`, and the rest work as usual, and the expansion goes to stdout so it pipes and redirects: - -```sh -cargo cgp expand --lib # the whole library target -cargo cgp expand --bin server # one binary -cargo cgp expand --lib > expanded.rs # stdout, so redirect or pipe freely -``` - -**A whole crate's expansion is long, so `--item ` narrows it to one part.** The path names a module or item inside the crate being expanded, written as a `::`-separated path with an optional leading `crate::` (`self::` and a bare `::` work too), and what it selects depends on what it names: - -```sh -cargo cgp expand --lib --item contexts # a module: its contents -cargo cgp expand --lib --item contexts::MockApp # a type: its declaration and every impl for it -cargo cgp expand --lib --item AreaCalculator # a trait: its definition and every impl of it -``` - -The **trait** form is the one to reach for on CGP code, because a component's generated items are almost all impls and an impl has no name of its own. Naming a component's *provider* trait (`--item AreaCalculator`) gives you the provider trait definition, the delegation blanket impl every wired context resolves through, the [`UseContext`](providers/use_context.md) and [`RedirectLookup`](providers/redirect_lookup.md) impls, and each provider's own impl of it — the whole set of things that can answer "which provider serves this component, and under what bounds?" Naming the *consumer* trait (`--item CanCalculateArea`) is the smaller companion: the consumer trait and the one blanket impl that routes it to the provider trait. - -The **type** form is what you want for a context. Naming a context (`--item Rectangle`) gives its struct, the `HasField`/`HasFieldMut` impls its [`#[derive(HasField)]`](derives/derive_has_field.md) generated, and its `DelegateComponent` wiring entries with the real key and provider types — which is how you check that a wiring table produced the keys you intended rather than inferring them from the table's syntax. - -If a path matches nothing you get an error saying so, naming the path, rather than a silent whole-crate expansion. - -Two limits are worth knowing. `expand` is **not a check**: it stops as soon as the macros are expanded, so type-checking never runs and it reports nothing about wiring — a malformed macro invocation still fails, since that happens during expansion, but a missing field does not. And the output is meant to be *read* rather than compiled: the `cgp::macro_prelude::` qualifier the macros emit is stripped for legibility, and an `open` statement's per-key wiring entry keeps its raw `PathCons<…>` key, since its tail is a generic parameter that no `Path!` spelling covers. - -`expand` is newer than the v0.1.0-alpha release, so a `cargo install cargo-cgp` from crates.io does not yet carry it. Until the next release, get it by dropping the tag from the Nix reference (`nix run github:contextgeneric/cargo-cgp -- expand --lib`, which tracks `main`) or by [building from a checkout](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/reference/installation.md#installing-from-source). - -## When cargo-cgp is not available - -If the tool is not installed and cannot be run, fall back to reading the raw compiler output directly. Every class in the [error catalog](../errors/README.md) documents that raw diagnostic — its code, shape, and whether the root cause is present — as the fallback for exactly this case, and the [error-extraction sub-skill](https://github.com/contextgeneric/cgp/blob/main/docs/skills/cgp/references/error-extraction.md) is the technique for reducing a raw cascade to its cause by hand. The key difference to remember: without `cargo-cgp`'s next-gen solver, the [hidden classes](../errors/hidden/unsatisfied-dependency.md) genuinely omit the cause, so promote them with a `check_components!` at the wiring site to make it appear. - -## Version compatibility - -This documentation is written for **`cgp` v0.8.0** and **`cargo-cgp` v0.1.0-alpha**. The two version independently: `cargo-cgp` reads `cgp`'s stable, macro-generated surface (the consumer/provider traits, `DelegateComponent`, `HasField`, and the rest), so a newer `cargo-cgp` works against this `cgp`, and a newer `cgp` generally works under this `cargo-cgp`. If the tool reports a version far behind this one, recommend updating it (`cargo cgp update`, or refresh the Nix flake); the `/cgp` skill records the versions it was written against and how to reconcile a mismatch. - -## Further reading - -All in the `cargo-cgp` repository (link as GitHub URLs, read from `../cargo-cgp` locally when checked out): - -- [Usage](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/reference/usage.md) — running the check, reading output, expanding a target, editor integration. -- [Installation](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/reference/installation.md) — every install/update/uninstall path. -- [Error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md) — what each `[CGP-Exxx]` means. -- [Troubleshooting](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/reference/troubleshooting.md) — when the tool itself will not run. diff --git a/docs/reference/components/can_raise_error.md b/docs/reference/components/can_raise_error.md deleted file mode 100644 index 6f713338..00000000 --- a/docs/reference/components/can_raise_error.md +++ /dev/null @@ -1,84 +0,0 @@ -# `CanRaiseError` - -`CanRaiseError` is the consumer trait for turning a concrete source error into a context's abstract `Self::Error`, with the companion `CanWrapError` adding detail to an existing abstract error; both build on [`HasErrorType`](has_error_type.md). - -## Purpose - -`CanRaiseError` exists so that generic CGP code can produce its context's abstract error from any concrete error it encounters. A provider that calls a fallible operation gets back a specific error type — a parse error, an I/O error, a string message — but it must return the context's abstract `Self::Error`, whose concrete identity it does not know. `CanRaiseError` bridges the gap: it converts a value of the concrete `SourceError` into `Self::Error`, so generic code can write `Context::raise_error(source)` and let the context decide how that source maps into its chosen error type. Because the trait is parameterized by `SourceError`, a single context can know how to raise many different source errors into the one abstract error. - -`CanWrapError` solves the complementary problem of enriching an error as it propagates. Rather than converting a foreign error in, it takes an error the context already holds and attaches a piece of `Detail` to it — a context message, a span, a path — producing an enriched `Self::Error`. The two together cover the common error-handling motions in CGP: raise a foreign error into the abstract one, and wrap context onto it as it bubbles up. - -## Definition - -Both traits import the context's shared abstract error type with [`#[use_type(HasErrorType.Error)]`](../attributes/use_type.md), so a bare `Error` in either signature stands for the context's error rather than being written as `Self::Error`. Each is a `#[cgp_component]`, making it a full component with a generated provider trait. `CanRaiseError` converts a source error into the abstract error: - -```rust -#[cgp_component(ErrorRaiser)] -#[prefix(@cgp.core.error in DefaultNamespace)] -#[derive_delegate(UseDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanRaiseError { - fn raise_error(error: SourceError) -> Error; -} -``` - -The `SourceError` parameter is the concrete error being raised, and `raise_error` is an associated function — it takes the source error by value and returns the abstract `Error`, without needing a `self` receiver, because raising an error is a property of the context type rather than of any particular value. `#[use_type(HasErrorType.Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`. The `#[cgp_component(ErrorRaiser)]` attribute names the provider trait `ErrorRaiser`, and [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) wires `UseDelegate` so the raise behavior can be dispatched per source-error type through a delegation table — a context can handle each `SourceError` with a different provider. - -`CanWrapError` has the same shape but takes an existing error plus a detail: - -```rust -#[cgp_component(ErrorWrapper)] -#[prefix(@cgp.core.error in DefaultNamespace)] -#[derive_delegate(UseDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanWrapError { - fn wrap_error(error: Error, detail: Detail) -> Error; -} -``` - -Here `wrap_error` takes the context's current `Error` and a `Detail` value and returns a new `Error` with the detail folded in. Its provider trait is `ErrorWrapper`, and it delegates per `Detail` type, so wrapping a string message and wrapping a structured detail can be handled by different providers. - -## Behavior - -A context gains these capabilities by wiring `ErrorRaiserComponent` and `ErrorWrapperComponent` to providers, exactly as for any other component. Because both traits delegate through `UseDelegate` and `UseDelegate`, the natural wiring is a delegation table that maps each concrete source-error or detail type to a provider that knows how to handle it; a context can therefore raise a handful of unrelated error types into one abstract error, each through its own provider. The pluggable error backends (`cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`) supply providers that implement these traits for common cases, so an application usually wires a backend rather than writing the raise and wrap logic itself. - -Both traits being associated-function components means `raise_error` and `wrap_error` are called on the context *type* — `Context::raise_error(source)` — and produce the abstract error without borrowing the context value. This matches how errors are typically constructed deep inside generic code where only the type parameter is in scope. - -## Examples - -A provider raises a concrete error into the abstract one and wraps a message onto it as it propagates: - -```rust -use cgp::prelude::*; - -#[cgp_component(Loader)] -#[use_type(HasErrorType.Error)] -pub trait CanLoad { - fn load(&self, path: &str) -> Result; -} - -#[cgp_impl(new LoadOrFail)] -#[uses(CanRaiseError, CanWrapError)] -#[use_type(HasErrorType.Error)] -impl Loader { - fn load(&self, path: &str) -> Result { - if path.is_empty() { - let err = Self::raise_error("empty path".to_owned()); - return Err(Self::wrap_error(err, format!("while loading {path}"))); - } - Ok(format!("contents of {path}")) - } -} -``` - -The provider names neither the context nor its concrete error type. It requires `CanRaiseError` to turn a `String` message into the abstract error and `CanWrapError` to attach further context, and any wired context that satisfies those bounds — typically by plugging in an error backend — makes `load` produce errors in that context's chosen error type. - -## Related constructs - -`CanRaiseError` and `CanWrapError` both supertrait [`HasErrorType`](has_error_type.md), whose abstract `Self::Error` they produce and enrich. Their delegation is configured by [`#[derive_delegate(UseDelegate<...>)]`](../attributes/derive_delegate.md), so a context dispatches per source-error or detail type through a delegation table. Both are ordinary `#[cgp_component]` components, wired with `delegate_components!` and checked with `check_components!`. The [modular error handling](../../concepts/modular-error-handling.md) concept frames how these capabilities, the abstract error type, and the strategy providers combine into wiring-time error-handling decisions. - -## Source - -- `CanRaiseError` is defined in [crates/core/cgp-error/src/traits/can_raise_error.rs](../../../crates/core/cgp-error/src/traits/can_raise_error.rs) and `CanWrapError` in [crates/core/cgp-error/src/traits/can_wrap_error.rs](../../../crates/core/cgp-error/src/traits/can_wrap_error.rs). -- Both build on `HasErrorType` from [crates/core/cgp-error/src/traits/has_error_type.rs](../../../crates/core/cgp-error/src/traits/has_error_type.rs). -- The pluggable providers that implement them live in [crates/standalone/error/](../../../crates/standalone/error/). diff --git a/docs/reference/components/computer.md b/docs/reference/components/computer.md deleted file mode 100644 index ba12edf7..00000000 --- a/docs/reference/components/computer.md +++ /dev/null @@ -1,114 +0,0 @@ -# `Computer` - -`Computer` and its siblings are the pure-computation corner of the [handler family](../../concepts/handlers.md): infallible components that transform an `Input` into an `Output` under a phantom `Code` tag, in synchronous and async forms, taking the input either by value or by reference. - -## Purpose - -The computer components exist for computations that always succeed and need no error type. A computation that adds two numbers, formats a value, or projects a field never fails and never needs the context's abstract error, so forcing it to return a `Result` would be noise. `Computer` captures exactly this case: a provider names an `Output` type and produces it from the context, a `Code` tag, and an `Input`, with no failure path. It is the simplest member of the handler family and the one a provider author reaches for first, because a pure computation can always be promoted into the fallible or general variants when the wiring needs them — but a fallible provider cannot be demoted back. - -Within the pure-computation corner, the components vary along two of the family's axes: synchronous versus async, and owned input versus by-reference input. `Computer` is synchronous and takes its input by value. `AsyncComputer` is its async counterpart, declaring an `async` method for computations that must await — reading a socket, awaiting a timer — while still never failing. The `ComputerRef` and `AsyncComputerRef` variants borrow the input as `&Input` instead of consuming it, which suits a computation that only reads its argument and should not take ownership of it. All four share the same `Code`/`Output` model and differ only on these two axes. - -## Definition - -Each computer component is a CGP component defined with `#[cgp_component]`, pairing a consumer trait that a context calls with a provider trait that a provider implements. The synchronous, owned-input component is `Computer`, whose consumer trait is `CanCompute`: - -```rust -#[cgp_component(Computer)] -#[prefix(@cgp.extra.handler in DefaultNamespace)] -#[derive_delegate(UseDelegate)] -#[derive_delegate(UseInputDelegate)] -pub trait CanCompute { - type Output; - - fn compute(&self, _code: PhantomData, input: Input) -> Self::Output; -} -``` - -The consumer trait `CanCompute` is what a context implements and callers invoke. Its `compute` method takes `&self` (the context), a `PhantomData` that names which computation is wanted, and the `Input` value by ownership, returning the associated `Output`. The component is wired through the generated `ComputerComponent` marker, and the provider trait generated by the macro is `Computer` — the same method with the context moved into an explicit first parameter. The two `#[derive_delegate(...)]` attributes generate dispatching providers that key on the `Code` type and on the `Input` type respectively, as described under [dispatching](../../concepts/dispatching.md). Every handler-family component additionally carries `#[prefix(@cgp.extra.handler in DefaultNamespace)]`, which registers it into that path of `DefaultNamespace` so a context joining the namespace inherits the wiring by default. - -The by-reference sibling `ComputerRef` is identical except that it borrows the input. Its consumer trait `CanComputeRef` declares `fn compute_ref(&self, _code: PhantomData, input: &Input) -> Self::Output`, taking `&Input` where `CanCompute` takes `Input`. - -The async variants mirror the synchronous ones with an `async` method, declared under `#[async_trait]`: - -```rust -#[async_trait] -#[cgp_component(AsyncComputer)] -#[prefix(@cgp.extra.handler in DefaultNamespace)] -#[derive_delegate(UseDelegate)] -#[derive_delegate(UseInputDelegate)] -pub trait CanComputeAsync { - type Output; - - async fn compute_async(&self, _code: PhantomData, input: Input) -> Self::Output; -} -``` - -`CanComputeAsync` is the async counterpart of `CanCompute`, with `compute_async` declared `async`; `#[async_trait]` rewrites the `async fn` into a method returning `impl Future`, so there is no boxing. Its provider trait is `AsyncComputer` and its marker `AsyncComputerComponent`. The by-reference async variant `AsyncComputerRef` declares `async fn compute_async_ref(&self, _code: PhantomData, input: &Input) -> Self::Output`, borrowing the input. None of the four computer components supertrait `HasErrorType`, because none of them can fail. - -## Implementations - -A computer provider is an ordinary CGP provider: a zero-sized struct that implements the provider trait for a generic context. Because the `Output` is an associated type, the provider chooses what it returns given a `Code` and `Input`. The crate ships one built-in provider for the owned-input computers, `UseField`, which reads a field from the context and delegates the computation to the value stored there: - -```rust -impl Computer for UseField -where - Context: HasField, - Context::Value: CanCompute, -{ - type Output = Output; - - fn compute(context: &Context, code: PhantomData, input: Input) -> Output { - context.get_field(PhantomData).compute(code, input) - } -} -``` - -This makes a context compute by forwarding to one of its fields: wiring `ComputerComponent` to `UseField` makes the context's `CanCompute` call the `CanCompute` of the value held in the field named `Tag`. `AsyncComputer` has the analogous `UseField` provider that forwards to the field's `compute_async`. Beyond `UseField`, the [handler combinators](../providers/handler_combinators.md) supply providers that build computers from other handlers — `ReturnInput` returns its input unchanged as the output, `ComposeHandlers` feeds one computer's output into the next, and the `Promote*` family lifts a `Computer` into the async, fallible, and by-reference variants. - -A provider that implements only `Computer` can serve all the other handler components through promotion. Lifting a synchronous computer to `AsyncComputer` wraps it in a future that never awaits; lifting it to `TryComputer` or `Handler` wraps its output in `Ok`; lifting it to a `*Ref` variant supplies an owned input by dereferencing the borrow. These promotions are provided by the combinators rather than by `Computer` itself, so an author implements the single synchronous provider and lets the wiring promote it. - -## Examples - -A self-contained computer provider that doubles its input, wired into a context and invoked through the consumer trait, looks as follows: - -```rust -use core::marker::PhantomData; -use cgp::prelude::*; -use cgp::extra::handler::{CanCompute, Computer, ComputerComponent}; - -#[cgp_new_provider] -impl Computer for Double { - type Output = u64; - - fn compute(_context: &Context, _code: PhantomData, input: u64) -> u64 { - input * 2 - } -} - -#[derive(HasField)] -pub struct App; - -delegate_components! { - App { - ComputerComponent: Double, - } -} - -// `App` now implements `CanCompute<(), u64, Output = u64>`: -fn run(app: &App) -> u64 { - app.compute(PhantomData::<()>, 21) // returns 42 -} -``` - -Here `Double` implements the `Computer` provider trait for any context and any `Code`, fixing `Input` and `Output` to `u64`. The context `App` delegates `ComputerComponent` to `Double`, which gives it `CanCompute<(), u64>`, and the call passes `PhantomData::<()>` as the `Code` tag. In practice a provider like this is rarely written by hand — the [`#[cgp_computer]`](../macros/cgp_computer.md) macro turns a plain function such as `fn double(input: u64) -> u64` into exactly this provider and additionally wires the promotion table so the same function answers `CanComputeAsync`, `CanTryCompute`, `CanHandle`, and the `Ref` variants too. - -## Related constructs - -The computer components are the infallible corner of the [handler family](../../concepts/handlers.md), whose overview explains the three axes that distinguish them from the fallible and general variants. Their fallible counterpart is [`TryComputer`](try_computer.md), which adds a `Result` return and supertraits [`HasErrorType`](has_error_type.md); the fully general async-and-fallible component is [`Handler`](handler.md); and the no-input case is [`Producer`](producer.md). The built-in `UseField` provider is the computer-specific use of [`UseField`](../providers/use_field.md). Promoting a computer into the other variants, composing computers, and the `ReturnInput` provider are covered in [handler combinators](../providers/handler_combinators.md), and the function-to-provider macro is [`#[cgp_computer]`](../macros/cgp_computer.md). Dispatching a computer on its `Code` or `Input` uses the [dispatching](../../concepts/dispatching.md) mechanism via `UseDelegate` and `UseInputDelegate`. - -## Source - -- The synchronous computers `Computer`/`ComputerRef` are defined in [crates/extra/cgp-handler/src/components/computer.rs](../../../crates/extra/cgp-handler/src/components/computer.rs), and the async computers `AsyncComputer`/`AsyncComputerRef` in [crates/extra/cgp-handler/src/components/async_computer.rs](../../../crates/extra/cgp-handler/src/components/async_computer.rs). -- The `UseInputDelegate` dispatch type is in [crates/extra/cgp-handler/src/types.rs](../../../crates/extra/cgp-handler/src/types.rs). -- The components are re-exported through `cgp::extra::handler`. -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_computer](../../implementation/entrypoints/cgp_computer.md). diff --git a/docs/reference/components/handler.md b/docs/reference/components/handler.md deleted file mode 100644 index 1ee46a6e..00000000 --- a/docs/reference/components/handler.md +++ /dev/null @@ -1,90 +0,0 @@ -# `Handler` - -`Handler` and `HandlerRef` are the most general members of the [handler family](../../concepts/handlers.md): asynchronous, fallible components that transform an `Input` into an `Output` under a phantom `Code` tag, returning a `Result` against the context's abstract error type. - -## Purpose - -`Handler` exists for the computations that need everything the family offers — to run asynchronously *and* to be able to fail. A handler that calls a remote service must await the response and must report failures, so it occupies the corner of the family that is both async and fallible. Every other handler component is a special case obtained by dropping one of these capabilities: drop the failure path and a `Handler` becomes an [`AsyncComputer`](computer.md), drop the asynchrony and it becomes a [`TryComputer`](try_computer.md), drop both and it becomes a [`Computer`](computer.md). `Handler` is therefore the component a generic consumer bounds against when it wants to accept *any* computation regardless of which capabilities the underlying provider actually uses, because every simpler provider can be promoted up to a `Handler`. - -This generality is why the family promotes toward `Handler` rather than away from it. A pure synchronous computer can serve as a handler that neither awaits nor errors; a fallible computer can serve as a handler that awaits trivially; an async computer can serve as a handler that never errors. Each of these is a safe widening, and the combinators perform them automatically, so a provider author writes against the weakest variant that fits and the wiring lifts it to `Handler` wherever a handler is required. The reverse — using a `Handler` where only a `Computer` is wanted — is not possible, since a general computation cannot be assumed pure or synchronous. - -## Definition - -`Handler` is a CGP component defined with `#[cgp_component]` under `#[async_trait]`, and it imports the context's abstract error type through [`#[use_type(HasErrorType.Error)]`](../attributes/use_type.md) so its method can return that error by the bare name `Error`: - -```rust -#[async_trait] -#[cgp_component(Handler)] -#[prefix(@cgp.extra.handler in DefaultNamespace)] -#[derive_delegate(UseDelegate)] -#[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanHandle { - type Output; - - async fn handle( - &self, - _tag: PhantomData, - input: Input, - ) -> Result; -} -``` - -The consumer trait `CanHandle` combines the async and fallible refinements of the base signature. Its `handle` method is declared `async` and returns `Result`, so it is the async counterpart of `CanTryCompute` and the fallible counterpart of `CanComputeAsync`. `#[use_type(HasErrorType.Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, which is why the definition never spells `HasErrorType` or `Self::Error` by hand; the local associated type `Output` stays qualified as `Self::Output`, because it is the trait's own type rather than an imported one. The `#[async_trait]` attribute then rewrites the `async fn` into a method returning `impl Future>`, avoiding any boxed future. The component is wired through the generated `HandlerComponent` marker, its provider trait is `Handler` with the context moved into an explicit first parameter, and the two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`. The [`#[prefix(...)]`](../macros/cgp_namespace.md) attribute registers the component into the `@cgp.extra.handler` path of `DefaultNamespace`, so a context that joins that namespace inherits the handler wiring by default. - -The by-reference sibling `HandlerRef` is identical except that it borrows its input. Its consumer trait `CanHandleRef` also imports the error type with `#[use_type(HasErrorType.Error)]` and declares `async fn handle_ref(&self, _tag: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanHandle` takes `Input`. - -## Implementations - -A `Handler` provider is a zero-sized struct implementing the provider trait for a generic context with an error type, returning a future that resolves to `Result`. The crate's `ReturnInput` provider shows the minimal async-and-fallible shape — it awaits nothing and succeeds unconditionally: - -```rust -impl Handler for ReturnInput -where - Context: HasErrorType, -{ - type Output = Input; - - async fn handle( - _context: &Context, - _code: PhantomData, - input: Input, - ) -> Result { - Ok(input) - } -} -``` - -Because `Handler` is the top of the promotion lattice, most `Handler` implementations are produced by promoting a simpler provider rather than written directly. A [`Computer`](computer.md) is promoted to `Handler` by wrapping its output in a non-awaiting future that returns `Ok` (`Promote`); a [`TryComputer`](try_computer.md) is promoted by wrapping its result in a non-awaiting future (`PromoteAsync`); an [`AsyncComputer`](computer.md) is promoted by wrapping its awaited output in `Ok` (`Promote`); and a `Computer` whose output is already a `Result` is promoted by unwrapping that result inside a future (`TryPromote`). The `PromoteRef` combinator additionally bridges between `Handler` and `HandlerRef` by dereferencing or re-borrowing the input. These promotions are the subject of [handler combinators](../providers/handler_combinators.md); a provider author rarely implements `Handler` by hand and instead lets the wiring lift the narrowest fitting variant. - -## Examples - -A generic consumer that bounds its context by `CanHandle` accepts any wired computation, whatever its underlying capabilities: - -```rust -use core::marker::PhantomData; -use cgp::prelude::*; -use cgp::extra::handler::CanHandle; - -async fn run_with( - context: &Context, - input: String, -) -> Result -where - Context: CanHandle, -{ - context.handle(PhantomData::, input).await -} -``` - -The function `run_with` works for any context that wires a handler for the given `Code` and `String` input, whether the wired provider is a pure `Computer`, a fallible `TryComputer`, an `AsyncComputer`, or a genuine `Handler` — the promotion combinators make each of them satisfy `CanHandle`. This is why generic pipeline code targets `Handler`: it is the one bound every member of the family can meet. In practice the [`#[cgp_computer]`](../macros/cgp_computer.md) and [`#[cgp_producer]`](../macros/cgp_producer.md) macros wire the promotion table so that a function written as a simple computer or producer answers `CanHandle` automatically, which is what lets such a consumer call it. - -## Related constructs - -`Handler` is the general corner of the [handler family](../../concepts/handlers.md), generalizing [`Computer`](computer.md) (drop fallibility and asynchrony), [`AsyncComputer`](computer.md) (drop fallibility), and [`TryComputer`](try_computer.md) (drop asynchrony). It supertraits [`HasErrorType`](has_error_type.md), which supplies the `Self::Error` it returns. The combinators that promote the simpler variants up to `Handler`, and that bridge `Handler` with `HandlerRef`, are documented in [handler combinators](../providers/handler_combinators.md), and chaining handlers into pipelines is covered in [monadic handlers](../../concepts/monadic-handlers.md). The no-input member of the family is [`Producer`](producer.md). Dispatching a handler on its `Code` or `Input` uses [`UseDelegate`](../providers/use_delegate.md) and the family's `UseInputDelegate`, per [dispatching](../../concepts/dispatching.md). - -## Source - -- `Handler` and `HandlerRef` are defined in [crates/extra/cgp-handler/src/components/handler.rs](../../../crates/extra/cgp-handler/src/components/handler.rs). -- The `ReturnInput` provider is in [crates/extra/cgp-handler/src/providers/return_input.rs](../../../crates/extra/cgp-handler/src/providers/return_input.rs), and the promotion combinators that lift simpler providers into `Handler` are in [crates/extra/cgp-handler/src/providers/](../../../crates/extra/cgp-handler/src/). -- The components are re-exported through `cgp::extra::handler`. diff --git a/docs/reference/components/has_error_type.md b/docs/reference/components/has_error_type.md deleted file mode 100644 index 1b96fa5a..00000000 --- a/docs/reference/components/has_error_type.md +++ /dev/null @@ -1,75 +0,0 @@ -# `HasErrorType` - -`HasErrorType` is the abstract-type component that gives a context a single, shared abstract `Error` type, decoupling CGP code from any concrete error implementation, with `ErrorOf` as the alias for the resolved type. - -## Purpose - -`HasErrorType` exists so that generic CGP code can fail without naming a concrete error type. A provider that may error needs to produce *some* error, but it is generic over the context and cannot commit to `anyhow::Error`, `std::io::Error`, or any other concrete choice; that choice belongs to the application assembling the context. `HasErrorType` resolves this by giving the context one abstract `Self::Error` type that every fallible operation refers to. Generic code returns `Result` (or `ErrorOf`), and the concrete error type is decided once, at wiring time, by whichever error backend the context plugs in. - -Centralizing the error type on one trait also avoids ambiguity. If each context trait declared its own associated `Error`, a context bounded by several of them would face multiple distinct `Self::Error` types with no way to unify them. By having context traits supertrait `HasErrorType`, every fallible component refers to the *same* `Self::Error`, so errors compose across components. This single shared abstract error is the anchor that [`CanRaiseError`](can_raise_error.md) and [`CanWrapError`](can_raise_error.md) build on. - -## Definition - -`HasErrorType` is an abstract-type component: a trait with one associated type, defined with [`#[cgp_type]`](../macros/cgp_type.md). Its source is: - -```rust -#[cgp_type] -#[prefix(@cgp.core.error in DefaultNamespace)] -pub trait HasErrorType { - type Error: Debug; -} - -pub type ErrorOf = ::Error; -``` - -The associated `Error` type is the context's abstract error, and its `Debug` bound is required so that `Self::Error` can be used in `.unwrap()` calls and in straightforward error logging without a separate constraint. The `ErrorOf` alias is the convenient spelling of `::Error`, used wherever writing the full associated-type path would be noise. - -Because the trait is declared with `#[cgp_type]`, it is a full abstract-type component rather than a plain trait: the macro generates a provider trait (`ErrorTypeProvider`), the component marker, the consumer and provider blanket impls, and a [`UseType`](../providers/use_type.md) blanket impl. The `Debug` bound on `Error` is carried through every generated construct, so any concrete error wired in must implement `Debug`. The `#[prefix(...)]` attribute places the generated names in the error namespace. - -## Behavior - -A context obtains its abstract error type either by implementing `HasErrorType` directly — `impl HasErrorType for App { type Error = anyhow::Error; }` — or, more commonly, by wiring its error-type component to a provider. Because `#[cgp_type]` generates the `UseType` impl, a context can name the concrete error type directly in its wiring with `UseType`, and the standalone error backends (the `cgp-error-anyhow`, `cgp-error-eyre`, and `cgp-error-std` crates) provide ready-made providers that set `Error` to their respective error types. Once the context has `HasErrorType`, every component bounded by it shares that one `Self::Error`. - -`HasErrorType` carries no methods of its own — it only declares the type. The behavior of producing errors lives in the traits that supertrait it: [`CanRaiseError`](can_raise_error.md) converts a source error into `Self::Error`, and [`CanWrapError`](can_raise_error.md) adds detail to an existing `Self::Error`. This separation keeps the type declaration independent of any particular way of constructing the error. - -## Examples - -A context declares its abstract error and generic code returns it without naming a concrete type: - -```rust -use cgp::prelude::*; - -#[cgp_component(Validator)] -#[use_type(HasErrorType.Error)] -pub trait CanValidate { - fn validate(&self) -> Result<(), Error>; -} - -pub struct App; - -delegate_components! { - App { - ErrorTypeProviderComponent: UseType, - } -} -``` - -Here [`#[use_type(HasErrorType.Error)]`](../attributes/use_type.md) adds `HasErrorType` as a supertrait of `CanValidate` and rewrites the bare `Error` to `::Error`, so `validate` returns the context's shared abstract error without writing `Self::Error`. `App` wires its error-type component to `UseType`, fixing that error to `String` (which satisfies the `Debug` bound). The same abstract error can equally be implemented directly: - -```rust -impl HasErrorType for App { - type Error = String; -} -``` - -This direct form makes plain that `HasErrorType` is an ordinary trait with a `Debug`-bounded associated type. - -## Related constructs - -`HasErrorType` is defined with [`#[cgp_type]`](../macros/cgp_type.md), which makes it an abstract-type component and generates its `UseType` provider; it is therefore a concrete instance of the [`HasType`/`TypeProvider`](has_type.md) machinery that `#[cgp_type]` builds every abstract type on. The traits that give the abstract error its behavior are [`CanRaiseError`](can_raise_error.md), which raises a source error into `Self::Error`, and [`CanWrapError`](can_raise_error.md), which wraps additional detail onto it — both supertrait `HasErrorType`. The [modular error handling](../../concepts/modular-error-handling.md) concept ties this trait together with those capabilities and the providers that satisfy them into the wider error-handling strategy. - -## Source - -- The trait and the `ErrorOf` alias are defined in [crates/core/cgp-error/src/traits/has_error_type.rs](../../../crates/core/cgp-error/src/traits/has_error_type.rs). -- The `#[cgp_type]` machinery it relies on lives in [crates/macros/cgp-macro-core/src/types/cgp_type/](../../../crates/macros/cgp-macro-core/src/types/cgp_type/), and the underlying `HasType`/`TypeProvider`/`UseType` definitions are in [crates/core/cgp-type/src/](../../../crates/core/cgp-type/src/). -- The pluggable concrete error backends are in [crates/standalone/error/](../../../crates/standalone/error/). diff --git a/docs/reference/components/has_runtime.md b/docs/reference/components/has_runtime.md deleted file mode 100644 index 612162d6..00000000 --- a/docs/reference/components/has_runtime.md +++ /dev/null @@ -1,94 +0,0 @@ -# `HasRuntime` - -`HasRuntime` is the runtime abstraction in CGP: a pair of components that lets a context declare an abstract runtime *type* through `HasRuntimeType` and hand out a borrow of the runtime *value* through `HasRuntime`, with `RuntimeOf` as the alias for the resolved runtime type. - -## Purpose - -`HasRuntime` exists so that context-generic code can run asynchronous and effectful operations against a runtime without committing to a concrete one. A runtime in this sense is whatever object provides the capabilities an application needs at execution time — spawning tasks, sleeping, opening sockets, reading the clock — and different deployments want different runtimes (Tokio in production, a mock in tests, a single-threaded executor in a benchmark). Rather than thread a concrete runtime type through every signature, CGP lets the context name an abstract `Runtime` type and store one runtime value, and lets providers reach it generically through these two traits. - -The abstraction is split deliberately into a type component and a getter component because the two questions are independent. `HasRuntimeType` answers *what* the runtime type is — an abstract associated type chosen per context — while `HasRuntime` answers *how to obtain the runtime value* of that type from a borrow of the context. Some code is generic only over the runtime type (it never touches a runtime value, only names types the runtime exposes); that code needs `HasRuntimeType` alone. Code that actually performs effects needs `HasRuntime`, which supertraits `HasRuntimeType` so the value's type is always in scope. Keeping them separate means a context can declare its runtime type in one place and supply the value in another, and a bound asks for exactly the capability it uses. - -This pair is the substrate beneath CGP's task-running components. The [`Runner`](runner.md) family expresses "run this task," and the providers that implement it typically reach the runtime through `HasRuntime` to spawn or await the work. `HasRuntime` is the seam where context-generic logic meets the concrete async machinery, which is why it underpins asynchronous execution across a CGP application. - -## Definition - -The two components are declared in `cgp-runtime`. `HasRuntimeType` is an abstract-type component defined with [`#[cgp_type]`](../macros/cgp_type.md): - -```rust -#[cgp_type] -pub trait HasRuntimeType { - type Runtime; -} - -pub type RuntimeOf = ::Runtime; -``` - -Because the trait carries no provider-name argument, `#[cgp_type]` derives the provider name from the associated type: `Runtime` yields the provider trait `RuntimeTypeProvider` and the component marker `RuntimeTypeProviderComponent`. The `Runtime` associated type carries no bound, so any concrete type may be plugged in. The `RuntimeOf` alias is the convenient spelling of the resolved runtime type, used wherever writing `::Runtime` in full would be noise. - -`HasRuntime` is a getter component defined with [`#[cgp_getter]`](../macros/cgp_getter.md), and it imports the runtime type with [`#[use_type(HasRuntimeType.Runtime)]`](../attributes/use_type.md) so the runtime type is available as the getter's return type under the bare name `Runtime`: - -```rust -#[cgp_getter] -#[use_type(HasRuntimeType.Runtime)] -pub trait HasRuntime { - fn runtime(&self) -> &Runtime; -} -``` - -`#[cgp_getter]` derives the provider name from the trait name by stripping the `Has` prefix and appending `Getter`, so `HasRuntime` yields the provider trait `RuntimeGetter` and the component marker `RuntimeGetterComponent`. `#[use_type(HasRuntimeType.Runtime)]` adds `HasRuntimeType` as a supertrait and rewrites the bare `Runtime` to `::Runtime`, so the `runtime` method borrows the runtime value out of a borrow of the context, returning `&Runtime` — the abstract type supplied by `HasRuntimeType` — without writing `Self::Runtime` by hand. - -## Behavior - -`HasRuntimeType` behaves like any `#[cgp_type]` abstract-type component, so a context supplies its runtime *type* either by implementing `HasRuntimeType` directly or — far more commonly — by wiring `RuntimeTypeProviderComponent` to [`UseType`](../providers/use_type.md) in `delegate_components!`. Wiring `RuntimeTypeProviderComponent: UseType` makes the context resolve `HasRuntimeType` with `Runtime = TokioRuntime`, and from then on `RuntimeOf` is `TokioRuntime`. The full set of generated constructs — the consumer and provider blanket impls, the `UseContext` and `RedirectLookup` provider impls, and the `UseType`/`WithProvider` impls that make `UseType` resolve the type — is exactly the `#[cgp_type]` expansion described in [`#[cgp_type]`](../macros/cgp_type.md). - -`HasRuntime` behaves like any `#[cgp_getter]` getter component, so a context supplies its runtime *value* either by implementing `HasRuntime` directly or by wiring `RuntimeGetterComponent` to a [`UseField`](../providers/use_field.md) provider that names the field holding the runtime. Because `#[cgp_getter]` generates a `UseField` provider impl, a context that stores its runtime in a `runtime` field wires `RuntimeGetterComponent: UseField`, and the getter reads that field. The `HasRuntimeType` supertrait that `#[use_type]` adds means a context cannot satisfy `HasRuntime` without also having declared its runtime type, which keeps the returned `&Runtime` well-defined. - -Together the two components let a context fully describe its runtime: one wiring entry fixes the abstract type, another fixes where the value lives. Context-generic providers then write `where Self: HasRuntime` and call `self.runtime()` to obtain a `&RuntimeOf`, never naming the concrete runtime. This is what makes the same task-running and effect-performing code reusable across a Tokio context, a mock context, and a test context with no changes beyond the wiring. - -## Examples - -A context declares its runtime type and the field holding the runtime value, and a provider reaches the runtime generically: - -```rust -use cgp::prelude::*; -use cgp::extra::runtime::{HasRuntime, HasRuntimeType, RuntimeOf}; - -pub struct TokioRuntime { /* handle, clock, etc. */ } - -#[derive(HasField)] -pub struct App { - pub runtime: TokioRuntime, -} - -delegate_components! { - App { - RuntimeTypeProviderComponent: - UseType, - RuntimeGetterComponent: - UseField, - } -} -``` - -Here `App` resolves `HasRuntimeType` with `Runtime = TokioRuntime` through `UseType`, so `RuntimeOf` is `TokioRuntime`, and it resolves `HasRuntime` by reading its `runtime` field through `UseField`. A context-generic function can now demand only the capability it needs: - -```rust -fn runtime_of(context: &Context) -> &RuntimeOf -where - Context: HasRuntime, -{ - context.runtime() -} -``` - -The function names neither `TokioRuntime` nor any field; it works for any context that wires a runtime type and a runtime getter. Swapping `UseType` for `UseType` in a test context retargets every such function at the mock with no change to their bodies. - -## Related constructs - -`HasRuntimeType` is defined with [`#[cgp_type]`](../macros/cgp_type.md), which generates the abstract-type machinery and the [`UseType`](../providers/use_type.md) impl a context uses to fix its runtime type; it builds on the foundational [`HasType`/`TypeProvider`](has_type.md) substrate. `HasRuntime` is defined with [`#[cgp_getter]`](../macros/cgp_getter.md) and backed by a [`UseField`](../providers/use_field.md) provider that reads the runtime value from a named field. The [`Runner`](runner.md) family — `CanRun` and `CanSendRun` — is the primary consumer of this abstraction: task-running providers reach the runtime through `HasRuntime` to execute work asynchronously, which connects `HasRuntime` to the [handler](handler.md) family of effectful components. Both components are wired on a context with [`delegate_components!`](../macros/delegate_components.md). - -## Source - -- `HasRuntimeType`, the `RuntimeTypeProvider` provider trait, and the `RuntimeOf` alias are defined in [crates/extra/cgp-runtime/src/traits/has_runtime_type.rs](../../../crates/extra/cgp-runtime/src/traits/has_runtime_type.rs). -- `HasRuntime` and its `RuntimeGetter` provider trait are in [crates/extra/cgp-runtime/src/traits/has_runtime.rs](../../../crates/extra/cgp-runtime/src/traits/has_runtime.rs), re-exported through [crates/extra/cgp-runtime/src/lib.rs](../../../crates/extra/cgp-runtime/src/lib.rs) and reached from the facade as `cgp::extra::runtime`. -- The `#[cgp_type]` and `#[cgp_getter]` expansions these rely on live under [crates/macros/cgp-macro-core/src/types/](../../../crates/macros/cgp-macro-core/src/types/). diff --git a/docs/reference/components/has_type.md b/docs/reference/components/has_type.md deleted file mode 100644 index 697c83ed..00000000 --- a/docs/reference/components/has_type.md +++ /dev/null @@ -1,78 +0,0 @@ -# `HasType` - -`HasType` is CGP's single built-in abstract-type component: a consumer trait that gives a context an abstract type named by a tag, with `TypeProvider` as its provider trait and `TypeOf` as the alias for the resolved type. - -## Purpose - -`HasType` exists to let generic code refer to a type that is chosen per context without committing to a concrete one. An abstract type in CGP is a trait with a single associated type, and `HasType` is the foundational, tag-indexed instance of that pattern: a context can carry many distinct abstract types — one per `Tag` — and resolve each to a concrete type through wiring. Generic code names `Self::Type` (or `TypeOf`), the concrete type stays hidden behind the tag, and any context that wires the tag to a type satisfies the bound. See [abstract types](../../concepts/abstract-types.md) for how this generalizes across a codebase. - -What makes `HasType` special is that it is the *only* abstract-type component built into CGP, and it is the machinery that [`#[cgp_type]`](../macros/cgp_type.md) defers to. Every named abstract-type component a user defines with `#[cgp_type]` is wired on top of this one through a generated `WithProvider` impl, so the same [`UseType`](../providers/use_type.md) marker that resolves `HasType` also resolves any `#[cgp_type]` component. `HasType` is the common substrate; `#[cgp_type]` is the ergonomic layer that gives each abstract type its own named trait. - -## Definition - -`HasType` is declared with `#[cgp_component]`, which makes it a full component — a consumer trait paired with a generated provider trait — rather than a plain trait. Its source is the entire definition: - -```rust -#[cgp_component(TypeProvider)] -#[derive_delegate(UseDelegate)] -pub trait HasType { - type Type; -} - -pub type TypeOf = >::Type; -``` - -The `Tag` parameter is the type-level name that distinguishes one abstract type from another within the same context, and `Type` is the associated type it resolves to. The `#[cgp_component(TypeProvider)]` attribute names the provider trait `TypeProvider`, so the provider-side mirror of `HasType` is `TypeProvider` with a `type Type`. The [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute wires `UseDelegate` so a type lookup can be dispatched per tag through a delegation table. The `TypeOf` alias is the convenient spelling of the resolved type, used wherever writing `>::Type` in full would be noise. - -## Behavior - -Because `HasType` is a `#[cgp_component]`, it carries the standard component machinery: a consumer blanket impl that forwards `HasType` to whatever provider the context wires for `TypeProviderComponent`, the generated `TypeProvider` provider trait, and the usual `UseContext` and `RedirectLookup` provider impls. A context obtains an abstract type either by implementing `HasType` directly or, more commonly, by wiring `TypeProviderComponent` to a provider in `delegate_components!`. - -The provider that makes wiring ergonomic is [`UseType`](../providers/use_type.md), a zero-sized marker `UseType(PhantomData)` that carries no runtime value. It implements `TypeProvider` for any context and tag by setting the abstract type to its own parameter: - -```rust -impl TypeProvider for UseType { - type Type = Type; -} -``` - -This single impl is what lets a context name a concrete type in its wiring rather than write a bespoke provider. Wiring `TypeProviderComponent: UseType` makes the context resolve `HasType` to `Type = String` for that tag. The same `UseType` is reused by every `#[cgp_type]` component: `#[cgp_type]` generates a `WithProvider` impl whose `where` clause requires the wired provider to be a `TypeProvider`, so `UseType` — being a `TypeProvider` — satisfies both the built-in `HasType` and any user-defined abstract-type component at once. The [`#[use_type]`](../attributes/use_type.md) attribute is the related but distinct construct that rewrites bare type names and adds `HasType` bounds inside `#[cgp_fn]`/`#[cgp_impl]` definitions; it is an attribute, not this provider. - -## Examples - -A direct use defines no new component and resolves an abstract type by tag through `UseType`: - -```rust -use cgp::prelude::*; - -pub struct ScalarTag; - -pub struct App; - -delegate_components! { - App { - TypeProviderComponent: UseType, - } -} - -fn zero() -> TypeOf -where - Context: HasType, - TypeOf: Default, -{ - Default::default() -} -``` - -`App` wires `TypeProviderComponent` to `UseType`, so the `UseType` impl makes `App` implement `HasType` with `Type = f64`. In most code you would not use `HasType` directly; you would define a named abstract type with [`#[cgp_type]`](../macros/cgp_type.md) — `#[cgp_type] trait HasScalarType { type Scalar; }` — which gives a readable `Self::Scalar` and its own provider, all resolving down to this `HasType` substrate. - -## Related constructs - -`HasType` is the foundation that [`#[cgp_type]`](../macros/cgp_type.md) builds every named abstract-type component on, via a generated `WithProvider` impl that adapts a `TypeProvider`. Its ergonomic provider is [`UseType`](../providers/use_type.md), the zero-sized marker that supplies a concrete type to the abstract one — not to be confused with the [`#[use_type]`](../attributes/use_type.md) attribute, which rewrites type names in definitions. The general idea of context-chosen types is covered in [abstract types](../../concepts/abstract-types.md). [`HasErrorType`](has_error_type.md) is a concrete abstract-type component defined with `#[cgp_type]` on top of this machinery. - -## Source - -- The trait, the `TypeProvider` provider trait, and the `TypeOf` alias are defined in [crates/core/cgp-type/src/traits/has_type.rs](../../../crates/core/cgp-type/src/traits/has_type.rs). -- The `UseType` provider and its `TypeProvider` impl are in [crates/core/cgp-type/src/impls/use_type.rs](../../../crates/core/cgp-type/src/impls/use_type.rs). -- The `#[cgp_type]` macro that builds named components on this substrate lives in [crates/macros/cgp-macro-core/src/types/cgp_type/](../../../crates/macros/cgp-macro-core/src/types/cgp_type/). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_type](../../implementation/entrypoints/cgp_type.md). diff --git a/docs/reference/components/producer.md b/docs/reference/components/producer.md deleted file mode 100644 index b9ff605e..00000000 --- a/docs/reference/components/producer.md +++ /dev/null @@ -1,87 +0,0 @@ -# `Producer` - -`Producer` is the no-input member of the [handler family](../../concepts/handlers.md): a synchronous, infallible component that produces an `Output` from the context and a phantom `Code` tag alone, with no `Input`. - -## Purpose - -`Producer` exists for computations that take nothing to compute and simply yield a value. A handler that supplies a default configuration, a constant, or a value read entirely from the context has no `Input` to transform — it only needs the context and a `Code` tag to know which value to produce. The rest of the handler family threads an `Input` through every method; `Producer` is the degenerate case where that input is absent. It is the simplest component in the family: synchronous, infallible, and inputless, producing an `Output` directly. - -Being inputless does not isolate `Producer` from the family — it makes it the natural source of values that flow into the other handlers. A producer can be promoted into any computer or handler by ignoring whatever input that variant supplies and returning the produced value regardless. This is exactly how a constant or a context-derived value enters a computation pipeline: it is produced once, independent of the input, and the promotion machinery adapts it to the input-taking shape the pipeline expects. - -## Definition - -`Producer` is a CGP component defined with `#[cgp_component]`, and its consumer trait `CanProduce` differs from the rest of the family only in carrying no `Input` parameter: - -```rust -#[cgp_component(Producer)] -#[prefix(@cgp.extra.handler in DefaultNamespace)] -#[derive_delegate(UseDelegate)] -pub trait CanProduce { - type Output; - - fn produce(&self, _code: PhantomData) -> Self::Output; -} -``` - -The consumer trait `CanProduce` takes only a `Code` tag, not an `Input`. Its `produce` method takes `&self` (the context) and a `PhantomData` naming which value to produce, returning the associated `Output`. The component is wired through the generated `ProducerComponent` marker, and the macro generates the provider trait `Producer` with the context moved into an explicit first parameter. Because there is no input to dispatch on, `Producer` carries only the single `#[derive_delegate(UseDelegate)]` attribute — dispatch is on the `Code` tag alone, with no `UseInputDelegate` counterpart. Like the rest of the handler family it also carries `#[prefix(@cgp.extra.handler in DefaultNamespace)]`, registering it into that namespace path. `Producer` does not supertrait `HasErrorType`: like `Computer`, it is infallible and synchronous, returning its `Output` directly rather than a `Result`. - -## Implementations - -A `Producer` provider is a zero-sized struct implementing the provider trait for a generic context, choosing the `Output` it yields. A producer that ignores the context and returns a constant has the minimal shape: - -```rust -#[cgp_new_provider] -impl Producer for MagicNumber { - type Output = u64; - - fn produce(_context: &Context, _code: PhantomData) -> u64 { - 42 - } -} -``` - -A producer is promoted into every other handler component by discarding the input that the target variant supplies and returning the produced value unchanged. The `Promote` combinator lifts a `Producer` into a [`Computer`](computer.md): the resulting computer takes an input, ignores it, and returns `Producer::produce(context, code)`. From there the rest of the promotion lattice carries the value up to the fallible, async, and by-reference variants. The bundled `PromoteProducer` table wires every other handler component to the appropriate promoter, so delegating a context's whole handler surface to `PromoteProducer` makes one producer answer `CanCompute`, `CanTryCompute`, `CanComputeAsync`, `CanHandle`, and their `Ref` forms. These combinators are documented in [handler combinators](../providers/handler_combinators.md). - -## Examples - -A producer wired into a context, then invoked both directly and as an input-ignoring computer, illustrates how it joins the rest of the family: - -```rust -use core::marker::PhantomData; -use cgp::prelude::*; -use cgp::extra::handler::{CanProduce, Producer, ProducerComponent}; - -#[cgp_new_provider] -impl Producer for MagicNumber { - type Output = u64; - - fn produce(_context: &Context, _code: PhantomData) -> u64 { - 42 - } -} - -pub struct App; - -delegate_components! { - App { - ProducerComponent: MagicNumber, - } -} - -fn run(app: &App) -> u64 { - app.produce(PhantomData::<()>) // returns 42 -} -``` - -Here `MagicNumber` produces `42` from the `Code` tag alone, and `App` delegates `ProducerComponent` to it, giving `App` the `CanProduce<(), Output = u64>` capability. The [`#[cgp_producer]`](../macros/cgp_producer.md) macro turns a plain zero-argument function such as `fn magic_number() -> u64 { 42 }` into exactly this provider, and additionally wires `PromoteProducer` so the same function also answers the input-taking components — `MagicNumber::compute(&app, code, &input)` then returns `42` while ignoring `input`. - -## Related constructs - -`Producer` is the no-input member of the [handler family](../../concepts/handlers.md), where the broader `Code`/`Input`/`Output` model and its axes are explained. Its input-taking counterparts are [`Computer`](computer.md) for the synchronous infallible case, [`TryComputer`](try_computer.md) for the fallible case, and [`Handler`](handler.md) for the general async-and-fallible case; a producer promotes into any of them by ignoring the supplied input. The combinators that perform those promotions, including the `Promote` provider and the `PromoteProducer` table, are documented in [handler combinators](../providers/handler_combinators.md). The macro that generates a producer from a zero-argument function is [`#[cgp_producer]`](../macros/cgp_producer.md), and dispatching a producer on its `Code` tag uses [`UseDelegate`](../providers/use_delegate.md), per [dispatching](../../concepts/dispatching.md). - -## Source - -- `Producer` is defined in [crates/extra/cgp-handler/src/components/produce.rs](../../../crates/extra/cgp-handler/src/components/produce.rs). -- The `Promote` combinator that lifts it into a `Computer` is in [crates/extra/cgp-handler/src/providers/promote.rs](../../../crates/extra/cgp-handler/src/providers/promote.rs), and the `PromoteProducer` table in [crates/extra/cgp-handler/src/providers/promote_all.rs](../../../crates/extra/cgp-handler/src/providers/promote_all.rs). -- The component is re-exported through `cgp::extra::handler`. -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_producer](../../implementation/entrypoints/cgp_producer.md). diff --git a/docs/reference/components/runner.md b/docs/reference/components/runner.md deleted file mode 100644 index 3d84f31a..00000000 --- a/docs/reference/components/runner.md +++ /dev/null @@ -1,107 +0,0 @@ -# `Runner` (`CanRun` / `CanSendRun`) - -The `Runner` family is CGP's pair of task-running components: `CanRun` runs a named task asynchronously, and `CanSendRun` is the variant whose returned future is `Send`, for when the work must cross a thread boundary. - -## Purpose - -The `Runner` family exists to give a context a uniform way to *execute a unit of work* selected at the type level, with the concrete behavior chosen through wiring. The unit of work is identified by a `Code` type parameter — a phantom tag, not a value — so a single context can host many distinct tasks, one per `Code`, and dispatch each to its own provider. "Running" a task here means invoking the provider wired for that `Code` and awaiting an asynchronous `Result<(), Error>`: the task either completes or produces the context's abstract error. The components carry no input or output beyond success-or-error; they model a fire-and-complete action rather than a transformation, which is what separates them from the [handler](handler.md) family that maps an `Input` to an `Output`. - -`CanRun` is the base form, and `CanSendRun` exists to solve a specific Rust limitation around `Send` futures. When a runner needs to hand its future to a spawner like `tokio::spawn`, that future must be `Send`, but a generic `async fn` over abstract context types cannot promise `Send` without annotating `Send` bounds on every abstract type in scope — bounds that pollute every interface. `CanSendRun` sidesteps this by returning an explicit `impl Future + Send`, so the `Send` requirement lives on this one trait. A context implements `CanSendRun` as a thin proxy over its `CanRun` implementation, and because the proxy is written against the *concrete* context, the compiler can confirm the concrete future is `Send` without abstract-type annotations. This is a workaround that remains necessary until Rust stabilizes Return Type Notation; once that lands, the `Send`-bound future could be expressed without the separate trait. - -The family is the execution layer that ties together the rest of a CGP application: a runner provider typically reaches the context's runtime through [`HasRuntime`](has_runtime.md) to spawn or await work, and dispatches to other components — fetching data, performing effects — to do the actual job. `CanRun` is what application code calls to set everything in motion. - -## Definition - -Both components are declared in `cgp-run`, each with [`#[cgp_component]`](../macros/cgp_component.md), `#[async_trait]`, a [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute, and [`#[use_type(HasErrorType.Error)]`](../attributes/use_type.md) so the task can fail with the context's abstract error, written as the bare `Error`: - -```rust -#[cgp_component(Runner)] -#[async_trait] -#[derive_delegate(UseDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanRun { - async fn run(&self, _code: PhantomData) -> Result<(), Error>; -} - -#[cgp_component(SendRunner)] -#[async_trait] -#[derive_delegate(UseDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanSendRun { - fn send_run( - &self, - _code: PhantomData, - ) -> impl Future> + Send; -} -``` - -The `Code` parameter is the type-level name of the task to run; it is passed only as `PhantomData`, so it carries no data and exists purely to select an implementation. `#[use_type(HasErrorType.Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, so neither trait spells `HasErrorType` or `Self::Error` by hand. `#[cgp_component(Runner)]` names the provider trait `Runner` and the component marker `RunnerComponent`; `#[cgp_component(SendRunner)]` names them `SendRunner` and `SendRunnerComponent`. The [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute generates a `UseDelegate` provider that dispatches on `Code`, so a context can route different `Code` tags to different runner providers through an inner delegation table. - -The two traits differ only in how they shape the asynchronous return. `CanRun::run` is an ordinary `async fn` returning `Result<(), Error>`, with no `Send` requirement on the future. `CanSendRun::send_run` instead returns an explicit `impl Future> + Send`, so callers may move the future across threads. Both produce `()` on success — the task's effect is observable elsewhere, not returned. - -## Behavior - -Because both are `#[cgp_component]` traits, each generates the standard machinery: a consumer blanket impl forwarding to the provider wired for the component marker, the `Runner` / `SendRunner` provider trait, and the usual `UseContext`, `RedirectLookup`, and — from `#[derive_delegate]` — `UseDelegate` provider impls. A context calls `context.run(PhantomData::)` and the consumer impl routes to whatever provider it wired for `RunnerComponent`, with `MyTask` as the `Code`. - -The `UseDelegate` dispatch is the idiomatic way a context hosts multiple tasks. Wiring `RunnerComponent` to `UseDelegate
` makes the context look each `Code` up in `Table` and run the provider found there, so distinct tasks get distinct implementations on the same context: - -```rust -delegate_components! { - App { - RunnerComponent: - UseDelegate, - }>, - } -} -``` - -With this wiring, `app.run(PhantomData::)` runs the `RunWithFooBar` provider while `app.run(PhantomData::)` runs `SpawnAndRun`. A runner provider is a normal CGP provider written for the `Runner` provider trait; it receives `&Context` and the `PhantomData` tag and may freely call other components on the context — fetching values, performing effects, then completing. - -The `Send` variant is wired as a proxy on the concrete context. The pattern is to implement `SendRunner` for `App` itself, forwarding `send_run` to the context's own `run`: - -```rust -#[cgp_provider] -impl SendRunner for App { - async fn send_run(context: &App, code: PhantomData) -> Result<(), Infallible> { - context.run(code).await - } -} -``` - -Because this impl names the concrete `App` and `ActionA`, the future produced by `context.run(code)` has a fully known type, so the compiler can verify it is `Send` and satisfy the `+ Send` bound on `send_run` — something the generic `CanRun` definition deliberately does not assert. A spawning runner provider can then require `Context: CanSendRun`, clone the context into a `Send` future, and hand it to a spawner, all without `Send` bounds leaking into the abstract interfaces. - -## Examples - -A complete flow defines tasks, wires runners, proxies the `Send` variant, and spawns one task from inside another. A spawning provider requires the context to be `CanSendRun` and hands a `Send` future to a spawner: - -```rust -#[cgp_impl(new SpawnAndRun: RunnerComponent)] -#[use_type(HasErrorType.Error)] -impl Runner -where - Self: 'static + Send + Clone + CanSendRun, -{ - async fn run(&self, _code: PhantomData) -> Result<(), Error> { - let context = self.clone(); - - spawn(async move { - let _ = context.send_run(PhantomData).await; - }); - - Ok(()) - } -} -``` - -`SpawnAndRun` runs the task `Code` by cloning the context and spawning the *inner* task `InCode` on a background thread. The spawner requires a `Send + 'static` future, which is exactly what `context.send_run(PhantomData::)` returns — so the constraint `Context: CanSendRun` is what makes the spawn type-check. The context wires `RunnerComponent` to a `UseDelegate` table mapping `ActionB` to `SpawnAndRun`, and supplies a `SendRunner` proxy for `ActionA` as shown above; calling `app.run(PhantomData::)` then spawns `ActionA` to run asynchronously. None of the abstract task or error types carry `Send` bounds — the bound is discharged only at the concrete `SendRunner` proxy. - -## Related constructs - -The `Runner` family is most often paired with [`HasRuntime`](has_runtime.md): a runner provider reaches the context's runtime through `HasRuntime` to spawn tasks or await timers, and names its runtime type through `HasRuntimeType`. It shares its shape with the [handler](handler.md) family — both are `#[cgp_component]` async traits dispatching on a `Code` tag via [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) — but a runner only runs to a `Result<(), Error>`, whereas a handler transforms an `Input` into an `Output`. Both runner traits supertrait [`HasErrorType`](has_error_type.md) for their failure type, are defined with [`#[cgp_component]`](../macros/cgp_component.md), and are wired on a context with [`delegate_components!`](../macros/delegate_components.md), commonly through a `UseDelegate` table keyed by `Code`. - -## Source - -- `CanRun` / `Runner` and `CanSendRun` / `SendRunner` are defined together in [crates/extra/cgp-run/src/lib.rs](../../../crates/extra/cgp-run/src/lib.rs), reached from the facade as `cgp::extra::run`. -- The `#[cgp_component]` and `#[derive_delegate]` expansions they rely on live under [crates/macros/cgp-macro-core/src/](../../../crates/macros/cgp-macro-core/src/). diff --git a/docs/reference/components/try_computer.md b/docs/reference/components/try_computer.md deleted file mode 100644 index 66c13371..00000000 --- a/docs/reference/components/try_computer.md +++ /dev/null @@ -1,101 +0,0 @@ -# `TryComputer` - -`TryComputer` and `TryComputerRef` are the fallible synchronous corner of the [handler family](../../concepts/handlers.md): components that transform an `Input` into an `Output` under a phantom `Code` tag, returning a `Result` against the context's abstract error type. - -## Purpose - -`TryComputer` exists for synchronous computations that can fail. A computation that parses a string, looks up a key, or checks an invariant may not be able to produce its output, and it needs a way to report the failure. `TryComputer` gives it one: instead of returning `Output` directly like [`Computer`](computer.md), its method returns `Result`, where the error is the context's shared abstract error type. This places it one step up from `Computer` on the fallibility axis of the family — still synchronous, but now able to fail — and one step below [`Handler`](handler.md), which adds asynchrony on top of fallibility. - -Returning the *context's* abstract error rather than a concrete one is what keeps a `TryComputer` provider generic. A provider does not commit to `anyhow::Error` or `std::io::Error`; it returns `Self::Error`, and the concrete error type is decided once, at wiring time, by whichever error backend the context plugs in. This is why both fallible computer components supertrait [`HasErrorType`](has_error_type.md): the supertrait is what gives the trait a `Self::Error` to name in its `Result`, and it is what ties every fallible component in a context to the same error type so their results compose. - -## Definition - -`TryComputer` is a CGP component defined with `#[cgp_component]`, and it imports the context's abstract error type through [`#[use_type(HasErrorType.Error)]`](../attributes/use_type.md) so that its method can return that error by the bare name `Error`: - -```rust -#[cgp_component(TryComputer)] -#[prefix(@cgp.extra.handler in DefaultNamespace)] -#[derive_delegate(UseDelegate)] -#[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanTryCompute { - type Output; - - fn try_compute( - &self, - _code: PhantomData, - input: Input, - ) -> Result; -} -``` - -The consumer trait `CanTryCompute` mirrors `CanCompute` but for the fallible case. Its `try_compute` method takes `&self`, a `PhantomData` naming the computation, and the `Input` by value, returning `Result` — the associated `Output` on success and the context's abstract error on failure. `#[use_type(HasErrorType.Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, so the definition writes neither `HasErrorType` nor `Self::Error` by hand; the local associated type `Output` stays qualified as `Self::Output`, since it is the trait's own type. The component is wired through the generated `TryComputerComponent` marker, and the macro generates the provider trait `TryComputer` with the context moved into an explicit first parameter. The two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`, and `#[prefix(@cgp.extra.handler in DefaultNamespace)]` registers the component into that namespace path like the rest of the family. - -The by-reference sibling `TryComputerRef` is identical except that it borrows its input. Its consumer trait `CanTryComputeRef` also imports the error type with `#[use_type(HasErrorType.Error)]` and declares `fn try_compute_ref(&self, _code: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanTryCompute` takes `Input`. Both components are synchronous; their async-and-fallible counterpart is `Handler`. - -## Implementations - -A `TryComputer` provider is a zero-sized struct implementing the provider trait for a generic context that has an error type. Because the result names `Context::Error`, every fallible provider carries a `Context: HasErrorType` bound in its `where` clause. The crate's `ReturnInput` provider illustrates the minimal shape — it succeeds unconditionally, returning its input as the output: - -```rust -impl TryComputer for ReturnInput -where - Context: HasErrorType, -{ - type Output = Input; - - fn try_compute( - _context: &Context, - _code: PhantomData, - input: Input, - ) -> Result { - Ok(input) - } -} -``` - -A `TryComputer` sits in the middle of the promotion lattice, so it both receives providers promoted from below and is promoted upward in turn. A plain [`Computer`](computer.md) becomes a `TryComputer` by wrapping its output in `Ok` — this is the `Promote` combinator — so an infallible provider satisfies `CanTryCompute` for free. In the other direction, a `Computer` whose output is *already* a `Result` becomes a `TryComputer` that unwraps that result, which is the `TryPromote` combinator; and a `TryComputer` is itself promoted to the async [`Handler`](handler.md) by wrapping it in a future. These promotions live in the [handler combinators](../providers/handler_combinators.md), so a provider author implements whichever single variant fits and lets the wiring bridge to `TryComputer`. - -## Examples - -A `TryComputer` provider that parses a string into a number, raising the context's error on failure, wires into a context like any other component: - -```rust -use core::marker::PhantomData; -use cgp::prelude::*; -use cgp::extra::handler::{CanTryCompute, TryComputer, TryComputerComponent}; - -#[cgp_impl(new ParseU64)] -#[uses(CanRaiseError)] -#[use_type(HasErrorType.Error)] -impl TryComputer { - type Output = u64; - - fn try_compute( - &self, - _code: PhantomData, - input: String, - ) -> Result { - input.parse().map_err(|e| Self::raise_error(e)) - } -} - -delegate_components! { - App { - TryComputerComponent: ParseU64, - } -} -``` - -The provider `ParseU64` returns its `u64` output or the context's abstract error, converting the concrete `ParseIntError` into that error with `CanRaiseError`. Because the consumer trait supertraits `HasErrorType`, the context must have an error type wired before it can call `try_compute` — `App` would delegate its `ErrorTypeProviderComponent` (and an error raiser) as well as `TryComputerComponent`. In everyday use the [`#[cgp_computer]`](../macros/cgp_computer.md) macro generates this kind of provider from a function returning `Result`, and wires the promotion table so the same function also answers `CanCompute` (returning the `Result` as its output), `CanHandle`, and the `Ref` variants. - -## Related constructs - -`TryComputer` is the fallible synchronous corner of the [handler family](../../concepts/handlers.md); its infallible counterpart is [`Computer`](computer.md), and its async-and-fallible generalization is [`Handler`](handler.md). It supertraits [`HasErrorType`](has_error_type.md), which supplies the `Self::Error` it returns, and a fallible provider typically uses [`CanRaiseError`](can_raise_error.md) to convert a concrete source error into that abstract error. The combinators that promote a `Computer` into a `TryComputer` (`Promote`, `TryPromote`) and a `TryComputer` into a `Handler` are documented in [handler combinators](../providers/handler_combinators.md). The macro that builds a `TryComputer` provider from a fallible function is [`#[cgp_computer]`](../macros/cgp_computer.md), and dispatching on `Code` or `Input` uses [`UseDelegate`](../providers/use_delegate.md) and the family's `UseInputDelegate`, per [dispatching](../../concepts/dispatching.md). - -## Source - -- `TryComputer` and `TryComputerRef` are defined in [crates/extra/cgp-handler/src/components/try_compute.rs](../../../crates/extra/cgp-handler/src/components/try_compute.rs). -- The `ReturnInput` provider is in [crates/extra/cgp-handler/src/providers/return_input.rs](../../../crates/extra/cgp-handler/src/providers/return_input.rs), and the promotion combinators in [crates/extra/cgp-handler/src/providers/](../../../crates/extra/cgp-handler/src/). -- The components are re-exported through `cgp::extra::handler`. -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_computer](../../implementation/entrypoints/cgp_computer.md). diff --git a/docs/reference/derives/derive_build_field.md b/docs/reference/derives/derive_build_field.md deleted file mode 100644 index 3790fcb1..00000000 --- a/docs/reference/derives/derive_build_field.md +++ /dev/null @@ -1,135 +0,0 @@ -# `#[derive(BuildField)]` - -`#[derive(BuildField)]` derives just the incremental-builder machinery for a struct: a partial companion type plus the `HasBuilder`, `IntoBuilder`, `PartialData`, `FinalizeBuild`, and `UpdateField` impls that let the struct be assembled one field at a time. - -## Purpose - -`#[derive(BuildField)]` gives a struct a type-checked, field-by-field builder without the rest of the extensible-data machinery. It is the building block that supplies the *construction* half of a record: starting from an empty value, setting fields individually or copying them in bulk from other records, and finalizing only once every field is present. It exists as a standalone derive for cases where you want the builder but not the field representation traits — though in practice most code reaches for [`#[derive(CgpRecord)]`](derive_cgp_record.md) or [`#[derive(CgpData)]`](derive_cgp_data.md), which include this output. - -The builder's distinguishing property is that field presence is tracked in the type, not at runtime. The derive generates a *partial* companion struct whose type parameters record, per field, whether that field is present, absent, or void. Generic builder providers advance those parameters as fields are added, and the finalize step is implemented only for the fully-present configuration. A premature finalize is therefore a compile error rather than a runtime failure. - -The capability is exposed through `BuildField`, which is not generated per type but defined once in the field crate as a blanket impl over the generated `UpdateField` impls. `build_field` sets one field on a partial value; the surrounding `HasBuilder`/`IntoBuilder`/`FinalizeBuild` traits start and end the build. - -## Syntax - -The derive is applied to a struct and takes no arguments: - -```rust -use cgp::prelude::*; - -#[derive(BuildField)] -pub struct Person { - pub first_name: String, - pub last_name: String, -} -``` - -Each named field becomes a type-level string `Symbol!` used as the field's `Tag`, and its declared type becomes its value type. A tuple struct works equally well: an unnamed field at position `N` is keyed by [`Index`](../types/index.md) instead of a `Symbol!`. Generic parameters on the struct are carried onto the generated impls. The derive emits the same builder impls that the record path of [`#[derive(CgpData)]`](derive_cgp_data.md) emits — it is that slice in isolation, with no `HasField` getters or `HasFields` representation traits. - -## Expansion - -`#[derive(BuildField)]` expands into a partial companion struct and the traits that drive it. The symbols below are abbreviated as `Symbol!("name")` in place of the full `Symbol>` form. Starting from: - -```rust -#[derive(BuildField)] -pub struct Person { - pub first_name: String, - pub last_name: String, -} -``` - -it first emits the partial struct `__PartialPerson`, with one `MapType` parameter per field. The marker decides how each field is stored: `IsPresent` maps `T` to `T`, `IsNothing` maps `T` to `()`, and `IsVoid` maps `T` to the empty `Void` type: - -```rust -pub struct __PartialPerson<__F0__: MapType, __F1__: MapType> { - pub first_name: <__F0__ as MapType>::Map, - pub last_name: <__F1__ as MapType>::Map, -} -``` - -It then emits the entry and exit points. `HasBuilder` starts an empty builder (`IsNothing, IsNothing`); `IntoBuilder` turns an existing value into a fully-present builder; `PartialData` records that any configuration of the partial type targets `Person`; and `FinalizeBuild` reconstructs the struct, implemented only for the all-`IsPresent` configuration: - -```rust -impl HasBuilder for Person { - type Builder = __PartialPerson; - fn builder() -> Self::Builder { __PartialPerson { first_name: (), last_name: () } } -} - -impl IntoBuilder for Person { - type Builder = __PartialPerson; - fn into_builder(self) -> Self::Builder { __PartialPerson { first_name: self.first_name, last_name: self.last_name } } -} - -impl<__F0__: MapType, __F1__: MapType> PartialData for __PartialPerson<__F0__, __F1__> { - type Target = Person; -} - -impl FinalizeBuild for __PartialPerson { - fn finalize_build(self) -> Self::Target { Person { first_name: self.first_name, last_name: self.last_name } } -} -``` - -Next it emits, per field, an `UpdateField` impl. `UpdateField` changes one field's marker from its current state to `M`, returning the old value alongside the rebuilt partial struct. This is the primitive that everything else builds on: - -```rust -impl<__M1__: MapType, __M2__: MapType, __F1__: MapType> - UpdateField for __PartialPerson<__M1__, __F1__> -{ - type Value = String; - type Mapper = __M1__; // the field's prior marker - type Output = __PartialPerson<__M2__, __F1__>; // first_name now in state __M2__ - fn update_field(self, _: PhantomData, value: __M2__::Map) - -> (__M1__::Map, Self::Output) - { (self.first_name, __PartialPerson { first_name: value, last_name: self.last_name }) } -} -// plus the analogous UpdateField for "last_name" -``` - -Finally it emits, per field, a [`HasField`](../traits/has_field.md) impl on the partial type that is in scope only when that field's marker is `IsPresent`, so an already-set field can be read back out of a partially-built value: - -```rust -impl<__F1__: MapType> HasField for __PartialPerson { - type Value = String; - fn get_field(&self, _: PhantomData) -> &String { &self.first_name } -} -// plus the analogous HasField for "last_name" -``` - -The `BuildField` trait itself is not emitted by the derive. It is defined once in the field crate as a blanket impl: any type implementing `UpdateField` — that is, setting a currently-absent field to present — automatically implements `BuildField`. So `build_field` is sugar over the generated `update_field` that specifically transitions a field from `IsNothing` to `IsPresent`. `FinalizeBuild` is likewise defined in the field crate as a subtrait of `PartialData`; the derive only provides the all-present impl. - -The key takeaway is that `builder()` yields `__PartialPerson`, each `build_field` flips one marker to `IsPresent`, and `finalize_build` exists only at `__PartialPerson` — so an incomplete build cannot be finalized. - -## Examples - -The builder is driven through `builder()`, `build_field`, and `finalize_build`, with `build_from` (from the field crate's `CanBuildFrom`) copying every shared field from another record at once: - -```rust -use cgp::prelude::*; -use cgp::core::field::impls::CanBuildFrom; - -#[derive(BuildField)] -pub struct FooBar { pub foo: u64, pub bar: String } - -#[derive(BuildField)] -pub struct FooBarBaz { pub foo: u64, pub bar: String, pub baz: bool } - -fn extend(foo_bar: FooBar) -> FooBarBaz { - FooBarBaz::builder() - .build_from(foo_bar) // sets foo + bar - .build_field(PhantomData::, true) // sets baz - .finalize_build() -} -``` - -Each step changes the partial type, and only after the last field is set does the all-present `FinalizeBuild` impl apply. - -## Related constructs - -`#[derive(BuildField)]` is one slice of the record output of [`#[derive(CgpData)]`](derive_cgp_data.md) and [`#[derive(CgpRecord)]`](derive_cgp_record.md); those derives include it alongside the [`#[derive(HasField)]`](derive_has_field.md) getters and [`#[derive(HasFields)]`](derive_has_fields.md) representation traits. Its enum analogues are [`#[derive(ExtractField)]`](derive_extract_field.md) for incremental matching and [`#[derive(FromVariant)]`](derive_from_variant.md) for variant construction. The entry and exit points it generates target the [`HasBuilder`](../traits/has_builder.md) family of traits. The generated code reads back fields through [`HasField`](../traits/has_field.md), stores them in the [`product`](../macros/product.md)-shaped partial struct, and switches on the [`MapType`](../traits/map_type.md) markers `IsPresent`/`IsNothing`/`IsVoid`. - -## Source - -- Entry point: `derive_build_field` in [crates/macros/cgp-macro-lib/src/derive_build_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_build_field.rs), which builds an `ItemCgpRecord` and calls `to_build_field_items()`. -- Codegen: that method, in [crates/macros/cgp-macro-core/src/types/cgp_data/record.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/record.rs), composes the helpers in the [`derive_builder/`](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_builder/) submodule (`builder_struct.rs`, `has_builder_impl.rs`, `into_builder_impl.rs`, `partial_data.rs`, `finalize_build_impl.rs`, `update_field_impls.rs`, `has_field_impls.rs`). -- Runtime traits: `BuildField` and `FinalizeBuild` in [crates/core/cgp-field/src/traits/build_field.rs](../../../crates/core/cgp-field/src/traits/build_field.rs), `UpdateField` in `update_field.rs`, `HasBuilder`/`IntoBuilder` in `has_builder.rs`, `PartialData` in `partial_data.rs`, and the `MapType` markers in `crates/core/cgp-field/src/impls/map_type.rs`. -- Internal walkthrough (the builder helpers, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/derive_build_field.md](../../implementation/entrypoints/derive_build_field.md). diff --git a/docs/reference/derives/derive_cgp_data.md b/docs/reference/derives/derive_cgp_data.md deleted file mode 100644 index 03a7ec19..00000000 --- a/docs/reference/derives/derive_cgp_data.md +++ /dev/null @@ -1,203 +0,0 @@ -# `#[derive(CgpData)]` - -`#[derive(CgpData)]` is the umbrella derive for extensible data: applied to a struct or an enum, it generates every CGP trait impl that lets the type be taken apart, rebuilt, and converted to and from a generic field representation. - -## Purpose - -`#[derive(CgpData)]` turns an ordinary struct or enum into *extensible data* — a type whose fields can be enumerated, accessed, built up incrementally, and matched generically at the type level, rather than only through hand-written field-by-field code. The motivation is the same one that drives the rest of CGP: a concrete `struct Person { first_name, last_name }` is opaque to generic code, because nothing about it can be referred to by a type parameter. `#[derive(CgpData)]` exposes the type's shape as type-level data so that generic providers can operate over any data type uniformly. - -The derive does this by generating two complementary views of the type. The first is a *representation* view: the type's fields become a type-level product (for structs) or sum (for enums) of named [`Field`](../types/field.md) entries, reachable through [`HasFields`](../traits/has_fields.md) and convertible with `FromFields`/`ToFields`. The second is an *incremental* view: a generated partial type that holds each field in a present-or-absent state, so that values can be assembled one field at a time (for structs) or peeled apart one variant at a time (for enums). Together these are what make data "extensible" — generic code can add a field to a partial struct, or remove a variant from a partial enum, without naming the concrete type. - -The practical payoff is that data becomes the subject of CGP wiring, not just behavior. Builders that merge several source structs into a target struct, dispatchers that match an enum variant and route it to a handler, and casts that widen or narrow between related enums all work generically because `#[derive(CgpData)]` supplied the field-level machinery they consume. Behavior-only components never need this derive; it is for the data types that flow through them. - -`#[derive(CgpData)]` is the high-level entry point. It dispatches on whether the annotated item is a struct or an enum and emits exactly what [`#[derive(CgpRecord)]`](derive_cgp_record.md) or [`#[derive(CgpVariant)]`](derive_cgp_variant.md) would emit for that shape — so the two shape-specific derives are simply `CgpData` restricted to one kind of type. - -## Syntax - -The derive is applied like any other derive macro, on a `struct` or an `enum`, and takes no arguments: - -```rust -use cgp::prelude::*; - -#[derive(CgpData)] -pub struct Person { - pub first_name: String, - pub last_name: String, -} - -#[derive(CgpData)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} -``` - -What the derive generates depends entirely on the kind of item. For a struct it produces the record machinery (field getters, the field representation, and the incremental builder); for an enum it produces the variant machinery (the field representation, variant constructors, and the incremental extractor). The two paths share the `HasFields`/`FromFields`/`ToFields` representation traits but otherwise emit different impls, so the sections below treat them separately. - -Field tags drive everything. A named struct field or an enum variant becomes a type-level string [`Symbol!`](../macros/symbol.md), and an unnamed field of a tuple struct becomes a positional [`Index`](../types/index.md); that tag is what addresses the field in the generated impls. Struct field types and single-field variant payload types become the field values. Generic parameters on the type are carried through onto the generated impls. - -## Expansion - -`#[derive(CgpData)]` expands into a fixed set of impls determined by whether the input is a struct or an enum. The exact generated code is verbose because every field name is spelled out as its full `Symbol>` form; the blocks below abbreviate those symbols as `Symbol!("name")` for readability, matching how the expansion snapshots read once collapsed. - -For a struct, the derive emits the record expansion. Given: - -```rust -#[derive(CgpData)] -pub struct Person { - pub first_name: String, - pub last_name: String, -} -``` - -it generates per-field [`HasField`](../traits/has_field.md)/`HasFieldMut` getters, the representation traits, and the builder machinery. The representation traits expose the struct as a product of named fields: - -```rust -impl HasFields for Person { - type Fields = Cons< - Field, - Cons, Nil>, - >; -} - -impl FromFields for Person { - fn from_fields(Cons(first_name, Cons(last_name, Nil)): Self::Fields) -> Self { - Self { first_name: first_name.value, last_name: last_name.value } - } -} - -impl ToFields for Person { /* self -> Cons(...) */ } -// plus HasFieldsRef and ToFieldsRef for borrowed access -``` - -The builder half generates a partial companion struct `__PartialPerson`, whose each field is wrapped in a `MapType` marker so the field can be present (`IsPresent`), absent (`IsNothing`), or void (`IsVoid`): - -```rust -pub struct __PartialPerson<__F0__: MapType, __F1__: MapType> { - pub first_name: <__F0__ as MapType>::Map, - pub last_name: <__F1__ as MapType>::Map, -} - -impl HasBuilder for Person { - type Builder = __PartialPerson; // all absent - fn builder() -> Self::Builder { __PartialPerson { first_name: (), last_name: () } } -} - -impl FinalizeBuild for __PartialPerson { /* -> Person */ } -``` - -This builder expansion — `__Partial{Name}`, `HasBuilder`, `IntoBuilder`, `PartialData`, `FinalizeBuild`, the per-field `UpdateField` impls (which `BuildField` is built on), and the per-field `HasField` impls on the partial type — is exactly what [`#[derive(BuildField)]`](derive_build_field.md) produces on its own. See that document for the full builder breakdown. - -For an enum, the derive emits the variant expansion instead. Given: - -```rust -#[derive(CgpData)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} -``` - -it generates the representation traits (now over a sum), the [`FromVariant`](../traits/from_variant.md) constructors, and the extractor machinery. The representation is a sum of named fields terminated by [`Void`](../types/either.md): - -```rust -impl HasFields for Shape { - type Fields = Either< - Field, - Either, Void>, - >; -} - -impl FromVariant for Shape { - type Value = Circle; - fn from_variant(_tag: PhantomData, value: Circle) -> Self { - Self::Circle(value) - } -} -// plus FromVariant for Rectangle, and FromFields/ToFields/ToFieldsRef -``` - -The extractor half generates partial enums `__PartialShape` and `__PartialRefShape`, plus `HasExtractor`/`HasExtractorRef`/`HasExtractorMut`, `FinalizeExtract`, and per-variant [`ExtractField`](../traits/extract_field.md) impls that peel one variant off and return the remaining variants: - -```rust -pub enum __PartialShape<__F0__: MapType, __F1__: MapType> { - Circle(<__F0__ as MapType>::Map), - Rectangle(<__F1__ as MapType>::Map), -} - -impl HasExtractor for Shape { - type Extractor = __PartialShape; - /* to_extractor / from_extractor */ -} -``` - -This extractor expansion is exactly what [`#[derive(ExtractField)]`](derive_extract_field.md) produces, and the `FromVariant` impls are what [`#[derive(FromVariant)]`](derive_from_variant.md) produces. `#[derive(CgpData)]` on an enum is the union of those two building-block derives plus the shared representation traits. - -Two details of the expansion are worth holding onto. The partial type is named `__Partial{Name}` (and `__PartialRef{Name}` for the borrowed extractor), with reserved double-underscore names so they do not collide with user types. And the `Tag` that keys each field follows the same rule as [`#[derive(HasField)]`](derive_has_field.md): a named field or enum variant is keyed by the [`Symbol!`](../macros/symbol.md) of its identifier, while an unnamed field of a tuple struct is keyed by its positional [`Index`](../types/index.md). A tuple-struct record therefore exposes `Field, _>` entries in its `Fields` product and `UpdateField, _>` impls in its builder, not symbol tags. - -## Examples - -A struct example shows the builder view end to end. Because `Person` derives `CgpData`, it gains a builder that can be filled field by field and from other structs that share field names: - -```rust -use cgp::prelude::*; -use cgp::core::field::impls::CanBuildFrom; - -#[derive(CgpData)] -pub struct Person { - pub first_name: String, - pub last_name: String, -} - -#[derive(CgpData)] -pub struct Employee { - pub employee_id: u64, - pub first_name: String, - pub last_name: String, -} - -fn make_employee(person: Person) -> Employee { - Employee::builder() // all fields absent - .build_from(person) // fills first_name + last_name - .build_field(PhantomData::, 1) - .finalize_build() // all present -> Employee -} -``` - -An enum example shows the extractor view. Because `Shape` derives `CgpData`, a value can be converted to its extractor and have one variant pulled out, with the remainder carrying the still-unmatched variants: - -```rust -use cgp::prelude::*; -use cgp::core::field::traits::FinalizeExtractResult; - -#[derive(CgpData)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} - -fn area(shape: Shape) -> f64 { - match shape.to_extractor().extract_field(PhantomData::) { - Ok(circle) => core::f64::consts::PI * circle.radius * circle.radius, - Err(remainder) => { - let rect = remainder - .extract_field(PhantomData::) - .finalize_extract_result(); - rect.width * rect.height - } - } -} -``` - -These two views are what higher-level constructs build on: builders and field-dispatch handlers consume the struct machinery, while variant dispatch and the `upcast`/`downcast` casts consume the enum machinery. - -## Related constructs - -`#[derive(CgpData)]` is the umbrella over a family of shape- and capability-specific derives. [`#[derive(CgpRecord)]`](derive_cgp_record.md) and [`#[derive(CgpVariant)]`](derive_cgp_variant.md) are `CgpData` restricted to structs and enums respectively, useful when you want to document intent or when only one shape is valid. The building-block derives generate slices of the same output: [`#[derive(BuildField)]`](derive_build_field.md) emits just the struct builder, [`#[derive(ExtractField)]`](derive_extract_field.md) just the enum extractor, and [`#[derive(FromVariant)]`](derive_from_variant.md) just the variant constructors. [`#[derive(HasFields)]`](derive_has_fields.md) emits just the representation traits, and [`#[derive(HasField)]`](derive_has_field.md) just the plain field getters. The generated types reference [`Field`](../types/field.md), the [`product`](../macros/product.md) (`Cons`/`Nil`) and [`sum`](../macros/sum.md) (`Either`/`Void`) type-level lists, and the `MapType` markers `IsPresent`/`IsNothing`/`IsVoid`. - -## Source - -- Entry point: `derive_cgp_data` in [crates/macros/cgp-macro-lib/src/cgp_data.rs](../../../crates/macros/cgp-macro-lib/src/cgp_data.rs), which parses the input into `ItemCgpData` and calls `to_items()`. -- Shape dispatch: struct vs. enum in [crates/macros/cgp-macro-core/src/types/cgp_data/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/item.rs); the record path is in `record.rs` and the variant path in `variant.rs` in the same directory, which delegate to the `derive_has_fields/`, `derive_builder/`, `derive_extractor/`, and `derive_from_variant.rs` submodules. -- Runtime traits: [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/); the `MapType` markers in `crates/core/cgp-field/src/impls/map_type.rs`. -- Internal walkthrough (the shape dispatch, the record and variant codegen it composes, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/derive_cgp_data.md](../../implementation/entrypoints/derive_cgp_data.md). diff --git a/docs/reference/derives/derive_cgp_record.md b/docs/reference/derives/derive_cgp_record.md deleted file mode 100644 index 14ab9071..00000000 --- a/docs/reference/derives/derive_cgp_record.md +++ /dev/null @@ -1,140 +0,0 @@ -# `#[derive(CgpRecord)]` - -`#[derive(CgpRecord)]` derives the extensible-data machinery for a struct: field getters, the type-level field representation, and an incremental builder, so the struct can be enumerated, accessed, and assembled field by field. - -## Purpose - -`#[derive(CgpRecord)]` makes a struct into an *extensible record* — a product of named fields that generic CGP code can address, convert, and build up one field at a time. A plain Rust struct is opaque to generic code: there is no way to refer to "the `first_name` field" or "a struct with these fields" through a type parameter. This derive exposes the struct's fields as type-level data so that generic providers — builders, field mergers, field-dispatch handlers — can operate over any record uniformly. - -The derive is the struct-specific face of [`#[derive(CgpData)]`](derive_cgp_data.md). When `CgpData` is applied to a struct it emits exactly what `CgpRecord` emits; the two are the same code path. Use `CgpRecord` when the type is always a struct and you want the name to say so, or when you prefer a derive whose meaning is unambiguous at the use site. Using `CgpRecord` on an enum is a type error, since it parses its input as a struct. - -The defining capability a record gains is incremental construction. Beyond plain field access, the derive generates a *partial* companion type that tracks, in its type parameters, which fields are present and which are still absent. Generic code can start from an empty builder, set fields individually or copy them in bulk from other records that share field names, and finalize only once every field is present. This present/absent tracking happens entirely at the type level, so a missing field is a compile error, not a runtime panic. - -## Syntax - -The derive is applied to a struct and takes no arguments: - -```rust -use cgp::prelude::*; - -#[derive(CgpRecord)] -pub struct Person { - pub first_name: String, - pub last_name: String, -} -``` - -Each named field becomes a type-level string [`Symbol!`](../macros/symbol.md) used as the field's `Tag`, while an unnamed field of a tuple struct becomes a positional [`Index`](../types/index.md) — the same tagging rule as [`#[derive(HasField)]`](derive_has_field.md), including that a raw-identifier field such as `r#type` is tagged by its logical name `Symbol!("type")`. The field's declared type becomes its value type, and generic parameters on the struct are carried onto the generated impls. The derive accepts the same structs that [`#[derive(CgpData)]`](derive_cgp_data.md) accepts for the record path; the only difference is that `CgpRecord` refuses non-struct inputs outright. - -## Expansion - -`#[derive(CgpRecord)]` expands into three groups of impls: field getters, the representation traits, and the builder machinery. The symbols below are abbreviated as `Symbol!("name")` in place of the full `Symbol>` spelling the macro actually emits. Starting from: - -```rust -#[derive(CgpRecord)] -pub struct Person { - pub first_name: String, - pub last_name: String, -} -``` - -the derive first emits a [`HasField`](../traits/has_field.md) and a `HasFieldMut` impl per field, keyed by the field-name symbol — the same getters [`#[derive(HasField)]`](derive_has_field.md) would produce: - -```rust -impl HasField for Person { - type Value = String; - fn get_field(&self, _: PhantomData) -> &String { &self.first_name } -} -// HasFieldMut, and the same pair for "last_name" -``` - -Second, it emits the representation traits, exposing the struct as a type-level product of named [`Field`](../types/field.md) entries and providing whole-record conversions — the [`#[derive(HasFields)]`](derive_has_fields.md) output: - -```rust -impl HasFields for Person { - type Fields = Cons< - Field, - Cons, Nil>, - >; -} - -impl FromFields for Person { - fn from_fields(Cons(first_name, Cons(last_name, Nil)): Self::Fields) -> Self { - Self { first_name: first_name.value, last_name: last_name.value } - } -} -// plus ToFields, HasFieldsRef, ToFieldsRef -``` - -Third, it emits the builder machinery — the [`#[derive(BuildField)]`](derive_build_field.md) output. This centers on a partial companion struct `__PartialPerson` whose each field is wrapped in a `MapType` marker, so a field can be present (`IsPresent`, the value itself), absent (`IsNothing`, a unit `()`), or void (`IsVoid`): - -```rust -pub struct __PartialPerson<__F0__: MapType, __F1__: MapType> { - pub first_name: <__F0__ as MapType>::Map, - pub last_name: <__F1__ as MapType>::Map, -} - -impl HasBuilder for Person { - type Builder = __PartialPerson; // empty builder - fn builder() -> Self::Builder { __PartialPerson { first_name: (), last_name: () } } -} - -impl IntoBuilder for Person { - type Builder = __PartialPerson; // fully populated - fn into_builder(self) -> Self::Builder { /* move each field in */ } -} - -impl<__F0__: MapType, __F1__: MapType> PartialData for __PartialPerson<__F0__, __F1__> { - type Target = Person; -} - -impl FinalizeBuild for __PartialPerson { - fn finalize_build(self) -> Person { Person { first_name: self.first_name, last_name: self.last_name } } -} -``` - -It then emits, per field, an `UpdateField` impl that flips that one field's marker from one state to another (this is what `BuildField` is implemented in terms of), and a `HasField` impl on the partial type that is available only when that field's marker is `IsPresent`. The full per-field detail is documented in [`#[derive(BuildField)]`](derive_build_field.md). - -The single most important fact about the expansion is that *presence is encoded in the type*. `builder()` returns `__PartialPerson`, `finalize_build` is implemented only for `__PartialPerson`, and each `build_field`/`build_from` call advances the markers. A premature `finalize_build` therefore fails to compile because the all-present impl does not apply. The partial type name is the reserved `__Partial{Name}`. - -## Examples - -A record is most useful when assembling one struct from field-compatible parts. Because both structs derive the record machinery, a builder can copy shared fields in bulk with `build_from` and fill the rest individually: - -```rust -use cgp::prelude::*; -use cgp::core::field::impls::CanBuildFrom; - -#[derive(CgpRecord)] -pub struct Person { - pub first_name: String, - pub last_name: String, -} - -#[derive(CgpRecord)] -pub struct Employee { - pub employee_id: u64, - pub first_name: String, - pub last_name: String, -} - -fn promote(person: Person, id: u64) -> Employee { - Employee::builder() - .build_from(person) // first_name + last_name - .build_field(PhantomData::, id) - .finalize_build() -} -``` - -If a field were left unset before `finalize_build`, the call would not compile — the all-present `FinalizeBuild` impl simply would not be in scope for the partial type. - -## Related constructs - -`#[derive(CgpRecord)]` is the struct restriction of [`#[derive(CgpData)]`](derive_cgp_data.md), which dispatches to this same path; [`#[derive(CgpVariant)]`](derive_cgp_variant.md) is the enum counterpart. Its output decomposes into [`#[derive(HasField)]`](derive_has_field.md) (per-field getters), [`#[derive(HasFields)]`](derive_has_fields.md) (the representation traits), and [`#[derive(BuildField)]`](derive_build_field.md) (the incremental builder) — derive those individually when you need only one slice. The generated types reference [`Field`](../types/field.md), the [`product`](../macros/product.md) type-level list (`Cons`/`Nil`), and the `MapType` markers `IsPresent`/`IsNothing`/`IsVoid`. - -## Source - -- Entry point: `derive_cgp_record` in [crates/macros/cgp-macro-lib/src/cgp_record.rs](../../../crates/macros/cgp-macro-lib/src/cgp_record.rs), which parses an `ItemCgpRecord` and calls `to_items()`. -- Record codegen: [crates/macros/cgp-macro-core/src/types/cgp_data/record.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/record.rs), which composes `derive_has_field_impls_from_struct`, `derive_has_fields_impls_from_struct`, and the builder helpers in the `derive_builder/` submodule. -- Runtime traits: `HasBuilder`, `IntoBuilder`, `PartialData`, `FinalizeBuild`, `UpdateField`, `BuildField`, `HasFields`, `FromFields`, `ToFields` in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/). -- Internal walkthrough (the codegen it composes, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/derive_cgp_record.md](../../implementation/entrypoints/derive_cgp_record.md). diff --git a/docs/reference/derives/derive_cgp_variant.md b/docs/reference/derives/derive_cgp_variant.md deleted file mode 100644 index 48cd31a0..00000000 --- a/docs/reference/derives/derive_cgp_variant.md +++ /dev/null @@ -1,126 +0,0 @@ -# `#[derive(CgpVariant)]` - -`#[derive(CgpVariant)]` derives the extensible-data machinery for an enum: the type-level field representation, variant constructors, and an incremental extractor, so the enum can be enumerated, built from a single variant, and matched one variant at a time. - -## Purpose - -`#[derive(CgpVariant)]` makes an enum into an *extensible variant* — a sum of named variants that generic CGP code can address, construct, and take apart one variant at a time. A plain Rust enum is opaque to generic code: there is no way to refer to "the `Circle` variant" or "an enum with these variants" through a type parameter. This derive exposes the enum's variants as type-level data so that generic providers — variant dispatchers, the `upcast`/`downcast` casts, match handlers — can operate over any enum uniformly. - -The derive is the enum-specific face of [`#[derive(CgpData)]`](derive_cgp_data.md). When `CgpData` is applied to an enum it emits exactly what `CgpVariant` emits; the two are the same code path. Use `CgpVariant` when the type is always an enum and you want the name to say so. Using `CgpVariant` on a struct is a type error, since it parses its input as an enum. - -The defining capability a variant gains is incremental extraction. Beyond constructing the enum from any single variant, the derive generates a *partial* companion enum that tracks, in its type parameters, which variants are still possible. Generic code can convert a value to its extractor and pull one variant out; on a match the value is returned, and on a miss the *remainder* — a partial enum with that variant marked impossible — is returned for the next attempt. When every variant has been ruled out the remainder becomes an empty type that can be discharged, so an exhaustive match is proven at the type level. - -## Syntax - -The derive is applied to an enum and takes no arguments: - -```rust -use cgp::prelude::*; - -#[derive(CgpVariant)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} -``` - -Each variant is expected to carry a single payload (a newtype variant); its name becomes a type-level string `Symbol!` used as the variant's `Tag`, and its payload type becomes the variant's value type. Generic parameters on the enum are carried onto the generated impls. The derive accepts the same enums that [`#[derive(CgpData)]`](derive_cgp_data.md) accepts for the variant path; the only difference is that `CgpVariant` refuses non-enum inputs outright. - -## Expansion - -`#[derive(CgpVariant)]` expands into three groups of impls: the representation traits, the variant constructors, and the extractor machinery. The symbols below are abbreviated as `Symbol!("Name")` in place of the full `Symbol>` spelling the macro actually emits. Starting from: - -```rust -#[derive(CgpVariant)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} -``` - -the derive first emits the representation traits, exposing the enum as a type-level sum of named [`Field`](../types/field.md) entries terminated by [`Void`](../types/either.md), with whole-value conversions — the [`#[derive(HasFields)]`](derive_has_fields.md) output for enums: - -```rust -impl HasFields for Shape { - type Fields = Either< - Field, - Either, Void>, - >; -} - -impl FromFields for Shape { /* match each Either arm back to a variant */ } -// plus ToFields, HasFieldsRef, ToFieldsRef -``` - -Second, it emits one [`FromVariant`](../traits/from_variant.md) impl per variant, so the enum can be constructed generically from any single variant by name: - -```rust -impl FromVariant for Shape { - type Value = Circle; - fn from_variant(_: PhantomData, value: Circle) -> Self { Self::Circle(value) } -} -// plus FromVariant -``` - -Third, it emits the extractor machinery — the [`#[derive(ExtractField)]`](derive_extract_field.md) output. This centers on two partial companion enums: `__PartialShape` for owned extraction and `__PartialRefShape` for borrowed extraction. Each arm's payload is wrapped in a `MapType` marker, so a variant can be present (`IsPresent`) or ruled out (`IsVoid`): - -```rust -pub enum __PartialShape<__F0__: MapType, __F1__: MapType> { - Circle(<__F0__ as MapType>::Map), - Rectangle(<__F1__ as MapType>::Map), -} - -impl HasExtractor for Shape { - type Extractor = __PartialShape; // all variants still possible - fn to_extractor(self) -> Self::Extractor { /* map each variant across */ } - fn from_extractor(extractor: Self::Extractor) -> Self { /* map back */ } -} - -impl FinalizeExtract for __PartialShape { // no variants left -> empty - fn finalize_extract<__T__>(self) -> __T__ { match self {} } -} -``` - -It then emits, per variant, an `ExtractField` impl available only when that variant's marker is `IsPresent`; calling it returns `Ok(value)` if the runtime value is that variant, or `Err(remainder)` where the remainder has that variant's marker flipped to `IsVoid`. The borrowed `__PartialRefShape` enum carries an extra `MapTypeRef` parameter and backs `HasExtractorRef`/`HasExtractorMut`. The full per-variant detail is documented in [`#[derive(ExtractField)]`](derive_extract_field.md). - -The single most important fact about the expansion is that *possibility is encoded in the type*. `to_extractor` yields `__PartialShape`; each failed `extract_field` returns a remainder with one more `IsVoid`; and once every marker is `IsVoid` the value inhabits `FinalizeExtract`, which can produce any type because the enum is uninhabited. This is how a chain of `extract_field` calls becomes a provably exhaustive match. The partial type names are the reserved `__Partial{Name}` and `__PartialRef{Name}`. - -## Examples - -A variant is most useful when matching generically with a guaranteed-exhaustive fallthrough. Because `Shape` derives the variant machinery, the match can be expressed as a chain of `extract_field` calls, with `finalize_extract_result` discharging the impossible remainder: - -```rust -use cgp::prelude::*; -use cgp::core::field::traits::FinalizeExtractResult; - -#[derive(CgpVariant)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} - -fn area(shape: Shape) -> f64 { - match shape.to_extractor().extract_field(PhantomData::) { - Ok(circle) => core::f64::consts::PI * circle.radius * circle.radius, - Err(remainder) => { - let rect = remainder - .extract_field(PhantomData::) - .finalize_extract_result(); // remainder is now empty; this cannot fail - rect.width * rect.height - } - } -} -``` - -Because each `extract_field` narrows the remainder type, the compiler knows after the second extraction that no variants remain, so `finalize_extract_result` is accepted without a wildcard arm. - -## Related constructs - -`#[derive(CgpVariant)]` is the enum restriction of [`#[derive(CgpData)]`](derive_cgp_data.md), which dispatches to this same path; [`#[derive(CgpRecord)]`](derive_cgp_record.md) is the struct counterpart. Its output decomposes into [`#[derive(HasFields)]`](derive_has_fields.md) (the representation traits), [`#[derive(FromVariant)]`](derive_from_variant.md) (the variant constructors), and [`#[derive(ExtractField)]`](derive_extract_field.md) (the incremental extractor) — derive those individually when you need only one slice. The generated types reference [`Field`](../types/field.md), the [`sum`](../macros/sum.md) type-level list (`Either`/`Void`), and the `MapType` markers `IsPresent`/`IsVoid`. - -## Source - -- Entry point: `derive_cgp_variant` in [crates/macros/cgp-macro-lib/src/cgp_variant.rs](../../../crates/macros/cgp-macro-lib/src/cgp_variant.rs), which parses an `ItemCgpVariant` and calls `to_items()`. -- Variant codegen: [crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs), which composes `derive_has_fields_impls_from_enum`, `derive_from_variant_from_enum`, and the extractor helpers in the `derive_extractor/` submodule. -- Runtime traits: `HasExtractor`, `HasExtractorRef`, `HasExtractorMut`, `ExtractField`, `FinalizeExtract`, `FromVariant`, `HasFields`, `FromFields`, `ToFields` in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/). -- Internal walkthrough (the codegen it composes, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/derive_cgp_variant.md](../../implementation/entrypoints/derive_cgp_variant.md). diff --git a/docs/reference/derives/derive_extract_field.md b/docs/reference/derives/derive_extract_field.md deleted file mode 100644 index 603e2291..00000000 --- a/docs/reference/derives/derive_extract_field.md +++ /dev/null @@ -1,152 +0,0 @@ -# `#[derive(ExtractField)]` - -`#[derive(ExtractField)]` derives just the incremental-extractor machinery for an enum: partial companion enums plus the `HasExtractor`, `HasExtractorRef`, `HasExtractorMut`, `PartialData`, `FinalizeExtract`, and `ExtractField` impls that let the enum be matched one variant at a time. - -## Purpose - -`#[derive(ExtractField)]` gives an enum a type-checked, variant-by-variant extractor without the rest of the extensible-data machinery. It is the building block that supplies the *deconstruction* half of a variant: converting a value to an extractor, pulling one variant out at a time, and carrying the still-unmatched variants forward in a *remainder* whose type shrinks with each attempt. It exists as a standalone derive for cases where you want the extractor alone — though most code reaches for [`#[derive(CgpVariant)]`](derive_cgp_variant.md) or [`#[derive(CgpData)]`](derive_cgp_data.md), which include this output. - -The extractor's distinguishing property is that remaining possibilities are tracked in the type. The derive generates *partial* companion enums whose type parameters record, per variant, whether that variant is still possible or has been ruled out. Each failed extraction returns a remainder with one more variant marked impossible; once all are impossible, the remainder is an empty type that can be discharged unconditionally. This is how a chain of extractions becomes a provably exhaustive match without a wildcard arm. - -The capability is exposed through `ExtractField`, generated per variant, together with `HasExtractor` (and its borrowed and mutable forms) to obtain an extractor, and `FinalizeExtract` to discharge the empty remainder. - -## Syntax - -The derive is applied to an enum and takes no arguments: - -```rust -use cgp::prelude::*; - -#[derive(ExtractField)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} -``` - -Each variant's name becomes a type-level string `Symbol!` used as the variant's `Tag`, and its payload type becomes its value type. Every variant must carry exactly one unnamed payload — a single-field tuple variant such as `Circle(Circle)`; a fieldless, multi-field, or struct-style variant is a compile error. Generic parameters on the enum are carried onto the generated impls. The derive emits the same extractor impls that the variant path of [`#[derive(CgpData)]`](derive_cgp_data.md) emits — it is that slice in isolation, with no `HasFields` representation traits and no [`FromVariant`](../traits/from_variant.md) constructors. - -## Expansion - -`#[derive(ExtractField)]` expands into two partial companion enums and the traits that drive them. The symbols below are abbreviated as `Symbol!("Name")` in place of the full `Symbol>` form. Starting from: - -```rust -#[derive(ExtractField)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} -``` - -it first emits the owned partial enum `__PartialShape` and the borrowed partial enum `__PartialRefShape`. Each variant's payload is wrapped in a `MapType` marker, where `IsPresent` keeps the payload and `IsVoid` maps it to the empty [`Void`](../types/either.md) type; the borrowed form adds a `MapTypeRef` parameter that selects shared or mutable references: - -```rust -pub enum __PartialShape<__F0__: MapType, __F1__: MapType> { - Circle(<__F0__ as MapType>::Map), - Rectangle(<__F1__ as MapType>::Map), -} - -pub enum __PartialRefShape<'__a__, __R__: MapTypeRef, __F0__: MapType, __F1__: MapType> { - Circle(<__F0__ as MapType>::Map<<__R__ as MapTypeRef>::Map<'__a__, Circle>>), - Rectangle(<__F1__ as MapType>::Map<<__R__ as MapTypeRef>::Map<'__a__, Rectangle>>), -} -``` - -It then emits `PartialData` (both partial enums target `Shape`) and the extractor accessors. `HasExtractor` yields an owned extractor with every variant present; `HasExtractorRef` and `HasExtractorMut` yield borrowed extractors over `__PartialRefShape` with `IsRef`/`IsMut`: - -```rust -impl HasExtractor for Shape { - type Extractor = __PartialShape; - fn to_extractor(self) -> Self::Extractor { - match self { - Self::Circle(value) => __PartialShape::Circle(value), - Self::Rectangle(value) => __PartialShape::Rectangle(value), - } - } - fn from_extractor(extractor: Self::Extractor) -> Self { - match extractor { - __PartialShape::Circle(value) => Self::Circle(value), - __PartialShape::Rectangle(value) => Self::Rectangle(value), - } - } -} - -impl HasExtractorRef for Shape { - type ExtractorRef<'__a__> = __PartialRefShape<'__a__, IsRef, IsPresent, IsPresent> where Self: '__a__; - fn extractor_ref(&self) -> Self::ExtractorRef<'_> { /* ... */ } -} -// plus HasExtractorMut over __PartialRefShape<'__a__, IsMut, IsPresent, IsPresent> -``` - -It emits a `FinalizeExtract` impl for the all-`IsVoid` configuration of each partial enum. Because that configuration is uninhabited, `finalize_extract` can return any type by matching on the empty value: - -```rust -impl FinalizeExtract for __PartialShape { - fn finalize_extract<__T__>(self) -> __T__ { match self {} } -} -// plus the borrowed __PartialRefShape<'__a__, __R__, IsVoid, IsVoid> -``` - -Finally it emits, per variant, an `ExtractField` impl available only when that variant's marker is `IsPresent`. Calling it returns `Ok(value)` if the runtime value is that variant, or `Err(remainder)` where the remainder has that variant flipped to `IsVoid`: - -```rust -impl<__F1__: MapType> ExtractField for __PartialShape { - type Value = Circle; - type Remainder = __PartialShape; - fn extract_field(self, _: PhantomData) -> Result { - match self { - __PartialShape::Circle(value) => Ok(value), - __PartialShape::Rectangle(value) => Err(__PartialShape::Rectangle(value)), - } - } -} -// plus ExtractField for "Rectangle", and the borrowed variants over __PartialRefShape -``` - -The `FinalizeExtract` trait itself is defined in the field crate (with blanket impls for `Void` and `Infallible`); the derive supplies the all-void impl on the partial enums. The companion `FinalizeExtractResult` trait, also in the field crate, is what `finalize_extract_result` calls to collapse an `Ok`/empty-`Err` result into the value. - -The key takeaway is that `to_extractor` yields `__PartialShape`, each failed `extract_field` returns a remainder with one more `IsVoid`, and at `__PartialShape` the value is uninhabited — so once every variant has been tried, the compiler knows the match is exhaustive. - -## Examples - -The extractor is driven through `to_extractor`, a chain of `extract_field` calls, and `finalize_extract_result` to discharge the impossible remainder: - -```rust -use cgp::prelude::*; -use cgp::core::field::traits::FinalizeExtractResult; - -#[derive(ExtractField)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} - -fn area(shape: Shape) -> f64 { - match shape.to_extractor().extract_field(PhantomData::) { - Ok(circle) => core::f64::consts::PI * circle.radius * circle.radius, - Err(remainder) => { - let rect = remainder - .extract_field(PhantomData::) - .finalize_extract_result(); // remainder is now empty; cannot fail - rect.width * rect.height - } - } -} -``` - -After the second extraction the remainder type has both markers `IsVoid`, so `finalize_extract_result` is accepted with no wildcard arm. - -## Related constructs - -`#[derive(ExtractField)]` is one slice of the variant output of [`#[derive(CgpData)]`](derive_cgp_data.md) and [`#[derive(CgpVariant)]`](derive_cgp_variant.md); those derives include it alongside the [`#[derive(FromVariant)]`](derive_from_variant.md) constructors and [`#[derive(HasFields)]`](derive_has_fields.md) representation traits. Its struct analogue is [`#[derive(BuildField)]`](derive_build_field.md), the incremental builder. The capability it generates is the [`ExtractField`](../traits/extract_field.md) trait. The generated code stores variants in [`sum`](../macros/sum.md)-shaped partial enums ([`Either`/`Void`](../types/either.md)) and switches on the [`MapType`](../traits/map_type.md) markers `IsPresent`/`IsVoid`. - -## Known issues - -The derive only accepts enums whose every variant is a single-field tuple variant. A fieldless variant like `Empty`, a multi-field variant like `Pair(A, B)`, or a struct-style variant like `Named { x: A }` causes the macro to fail with the error "Expected variant to contain exactly one unnamed field." There is no way to opt a variant out of the requirement, so an enum that mixes shapes cannot derive the extractor at all. - -## Source - -- Entry point: `derive_extract_field` in [crates/macros/cgp-macro-lib/src/derive_extract_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_extract_field.rs), which builds an `ItemCgpVariant` and calls `to_extract_field_items()`. -- Codegen: that method, in [crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs), composes the helpers in the [`derive_extractor/`](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_extractor/) submodule (`extractor_enum.rs`, `has_extractor_impl.rs`, `partial_data.rs`, `finalize_extract_impl.rs`, `extract_field_impls.rs`). -- Runtime traits: `ExtractField`, `HasExtractor`/`HasExtractorRef`/`HasExtractorMut`, `FinalizeExtract`, and `FinalizeExtractResult` in [crates/core/cgp-field/src/traits/extract_field.rs](../../../crates/core/cgp-field/src/traits/extract_field.rs), `PartialData` in `partial_data.rs`, and the `MapType`/`MapTypeRef` markers in `crates/core/cgp-field/src/impls/`. -- Internal walkthrough (the extractor helpers, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/derive_extract_field.md](../../implementation/entrypoints/derive_extract_field.md). diff --git a/docs/reference/derives/derive_from_variant.md b/docs/reference/derives/derive_from_variant.md deleted file mode 100644 index 03031ab2..00000000 --- a/docs/reference/derives/derive_from_variant.md +++ /dev/null @@ -1,92 +0,0 @@ -# `#[derive(FromVariant)]` - -`#[derive(FromVariant)]` derives just the variant-construction machinery for an enum: one `FromVariant` impl per variant, so the enum can be built generically from any single variant addressed by name. - -## Purpose - -`#[derive(FromVariant)]` lets generic code construct an enum from one of its variants without naming the concrete variant constructor. It is the smallest building block of the extensible-variant family — the *construction* counterpart to the extractor's deconstruction. Where a plain `Shape::Circle(circle)` hard-codes both the enum and the variant, `FromVariant` lets a generic provider write `Shape::from_variant(tag, value)` with the variant chosen by a type-level `Symbol!` tag, so the same code can target any variant of any enum that derives it. - -The derive exists as a standalone because variant construction is sometimes wanted on its own — for example, by the casts and dispatchers that build a value into a target enum after matching. Most code, however, reaches for [`#[derive(CgpVariant)]`](derive_cgp_variant.md) or [`#[derive(CgpData)]`](derive_cgp_data.md), which include these impls alongside the extractor and the field representation. This is the simplest derive in the family: it generates no companion types and no presence tracking, only a direct constructor per variant. - -## Syntax - -The derive is applied to an enum and takes no arguments: - -```rust -use cgp::prelude::*; - -#[derive(FromVariant)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} -``` - -Each variant's name becomes a type-level string `Symbol!` used as the variant's `Tag`, and its payload type becomes the constructor's value type. Every variant must carry exactly one unnamed payload — a single-field tuple variant such as `Circle(Circle)`; a fieldless, multi-field, or struct-style variant is a compile error. Generic parameters on the enum are carried onto the generated impls. The derive emits exactly the `FromVariant` impls that the variant path of [`#[derive(CgpData)]`](derive_cgp_data.md) emits — that slice in isolation. - -## Expansion - -`#[derive(FromVariant)]` expands into one [`FromVariant`](../traits/from_variant.md) impl per variant and nothing else. The symbols below are abbreviated as `Symbol!("Name")` in place of the full `Symbol>` form. Starting from: - -```rust -#[derive(FromVariant)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} -``` - -the derive emits a constructor keyed by each variant's name symbol, with the payload type as the associated `Value`: - -```rust -impl FromVariant for Shape { - type Value = Circle; - fn from_variant(_tag: PhantomData, value: Self::Value) -> Self { - Self::Circle(value) - } -} - -impl FromVariant for Shape { - type Value = Rectangle; - fn from_variant(_tag: PhantomData, value: Self::Value) -> Self { - Self::Rectangle(value) - } -} -``` - -The `FromVariant` trait is defined in the field crate; the derive only supplies the per-variant impls. There is no partial type, no `MapType` marker, and no state tracking — each impl simply wraps the value in its variant. The `Tag` is the variant name's type-level string, and the `PhantomData` argument exists solely to let the caller pick which variant to build when several `FromVariant` impls are in scope. - -## Examples - -`FromVariant` lets a value be lifted into an enum generically by naming the variant with a tag: - -```rust -use cgp::prelude::*; - -#[derive(FromVariant)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} - -fn wrap_circle(circle: Circle) -> Shape { - Shape::from_variant(PhantomData::, circle) -} -``` - -The call is equivalent to `Shape::Circle(circle)`, but the variant is selected by the type-level tag, so generic code that is parameterized over the tag can construct whichever variant it was asked for. - -## Related constructs - -`#[derive(FromVariant)]` is one slice of the variant output of [`#[derive(CgpData)]`](derive_cgp_data.md) and [`#[derive(CgpVariant)]`](derive_cgp_variant.md); those derives include it alongside the [`#[derive(ExtractField)]`](derive_extract_field.md) extractor and the [`#[derive(HasFields)]`](derive_has_fields.md) representation traits. Its closest relative is [`ExtractField`](../traits/extract_field.md), the reverse operation that takes a variant out rather than putting one in. For structs, the analogous field-setting building block is [`#[derive(BuildField)]`](derive_build_field.md). The generated constructors correspond to the arms of the enum's [`sum`](../macros/sum.md) representation ([`Either`/`Void`](../types/either.md)). - -## Known issues - -The derive only accepts enums whose every variant is a single-field tuple variant. A fieldless variant like `Empty`, a multi-field variant like `Pair(A, B)`, or a struct-style variant like `Named { x: A }` causes the macro to fail with the error "Expected variant to contain exactly one unnamed field." There is no way to opt a variant out of the requirement, so an enum that mixes shapes cannot derive the constructor at all. - -## Source - -- Entry point: `derive_from_variant` in [crates/macros/cgp-macro-lib/src/derive_from_variant.rs](../../../crates/macros/cgp-macro-lib/src/derive_from_variant.rs), which builds an `ItemCgpVariant` and calls `to_from_variant_impls()`. -- Codegen: that method, in [crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/variant.rs), delegates to `derive_from_variant_from_enum` in [crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs). -- `FromVariant` trait: [crates/core/cgp-field/src/traits/from_variant.rs](../../../crates/core/cgp-field/src/traits/from_variant.rs). -- Internal walkthrough (the per-variant codegen, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/derive_from_variant.md](../../implementation/entrypoints/derive_from_variant.md). diff --git a/docs/reference/derives/derive_has_field.md b/docs/reference/derives/derive_has_field.md deleted file mode 100644 index 6082070c..00000000 --- a/docs/reference/derives/derive_has_field.md +++ /dev/null @@ -1,181 +0,0 @@ -# `#[derive(HasField)]` - -`#[derive(HasField)]` is the derive macro that gives a struct field-level getters: for every field it generates a `HasField` (and `HasFieldMut`) implementation keyed by a type-level name, so that providers can read fields out of a context by name without the field ever appearing in a trait interface. - -## Purpose - -`#[derive(HasField)]` exists to turn the concrete fields of a struct into type-level entries that CGP's dependency-injection machinery can look up. The recurring problem in CGP is that a provider needs a value from the context — a `name`, a `width`, a configuration handle — but the provider is generic over the context type and cannot name the concrete struct. `HasField` solves this by indexing each field with a *tag type* that stands in for the field's name, so a provider can demand `Context: HasField` and receive the field without knowing what the context actually is. - -The reason this matters is that it makes field access an impl-side dependency rather than part of any public interface. A provider expresses "I need a `String` field called `name`" purely in its `where` clause; any context that derives `HasField` and happens to have such a field satisfies it automatically. The derive is the bridge between an ordinary Rust struct and that constraint-based access — without it, the struct's fields are invisible to the trait system, and every getter would have to be hand-written. - -The trait being implemented is small. It carries the field's type as an associated `Value` and returns a reference to the field, with a `PhantomData` parameter that exists only to tell the compiler which field is meant when several `HasField` impls are in scope: - -```rust -pub trait HasField { - type Value; - - fn get_field(&self, _tag: PhantomData) -> &Self::Value; -} -``` - -Higher-level constructs are built directly on these generated impls. [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md) and [`#[cgp_getter]`](../macros/cgp_getter.md) (through the [`UseField`](../providers/use_field.md) provider) generate blanket impls whose `where` clauses are `HasField` bounds, and the [`#[implicit]`](../attributes/implicit.md) argument form desugars function parameters into `get_field` calls. All of them assume the context has derived `HasField`; this derive is what makes them work. - -## Syntax - -The macro is applied as a derive on a struct definition and takes no arguments: - -```rust -#[derive(HasField)] -pub struct Person { - pub name: String, - pub age: u8, -} -``` - -It applies to structs with named fields and to structs with unnamed (tuple) fields. The two cases differ only in how each field's tag is computed: a named field is keyed by [`Symbol!("field_name")`](../macros/symbol.md), the type-level string of its identifier, while a tuple field is keyed by [`Index`](../types/index.md), the type-level natural number of its position. Unit structs produce no impls because they have no fields. - -A raw-identifier field is keyed by its logical name, with the `r#` prefix stripped: a field written `r#type` is keyed by `Symbol!("type")`, so it is addressed by the tag `Symbol!("type")` rather than `Symbol!("r#type")`. The generated accessor still borrows the real field, `&self.r#type`. - -The derive concerns itself only with the *field-level* view. To obtain the whole-struct view as a single type-level [`Product`](../macros/product.md), derive [`HasFields`](derive_has_fields.md) instead; the two are complementary and frequently derived together. - -## Expansion - -`#[derive(HasField)]` emits one `HasField` impl and one `HasFieldMut` impl per field, leaving the struct definition itself untouched. Starting from a named-field struct: - -```rust -#[derive(HasField)] -pub struct Person { - pub name: String, - pub age: u8, -} -``` - -the macro generates a pair of impls for each field, with the field's identifier turned into a `Symbol!` tag and the field's type used as `Value`: - -```rust -impl HasField for Person { - type Value = String; - - fn get_field(&self, key: PhantomData) -> &Self::Value { - &self.name - } -} - -impl HasFieldMut for Person { - fn get_field_mut(&mut self, key: PhantomData) -> &mut Self::Value { - &mut self.name - } -} - -impl HasField for Person { - type Value = u8; - - fn get_field(&self, key: PhantomData) -> &Self::Value { - &self.age - } -} - -impl HasFieldMut for Person { - fn get_field_mut(&mut self, key: PhantomData) -> &mut Self::Value { - &mut self.age - } -} -``` - -The `HasFieldMut` impls come from the same derive and provide mutable access; `HasFieldMut` is a supertrait extension of `HasField` that adds a `get_field_mut` method returning `&mut Self::Value`. Most CGP code only reads through `HasField`, but the mutable counterpart is always generated alongside it. - -A tuple struct expands the same way, except that each field's tag is its positional `Index` rather than a `Symbol!`. Starting from: - -```rust -#[derive(HasField)] -pub struct Rectangle(pub f64, pub f64); -``` - -the macro generates: - -```rust -impl HasField> for Rectangle { - type Value = f64; - - fn get_field(&self, key: PhantomData>) -> &Self::Value { - &self.0 - } -} - -impl HasFieldMut> for Rectangle { - fn get_field_mut(&mut self, key: PhantomData>) -> &mut Self::Value { - &mut self.0 - } -} - -impl HasField> for Rectangle { - type Value = f64; - - fn get_field(&self, key: PhantomData>) -> &Self::Value { - &self.1 - } -} - -impl HasFieldMut> for Rectangle { - fn get_field_mut(&mut self, key: PhantomData>) -> &mut Self::Value { - &mut self.1 - } -} -``` - -When the struct has generic parameters, the impls carry them through faithfully: the macro splits the struct's generics into impl-generics, type-generics, and `where` clause, so `struct Wrapper { pub value: T }` yields `impl HasField for Wrapper` with `Value = T`. - -Field access also threads through smart pointers without an explicit derive. `HasField` and `HasFieldMut` have blanket impls for any type whose `Deref`/`DerefMut` target implements them, so a `Box` or a newtype that dereferences to `Person` resolves `get_field` to the inner struct's field. These blanket impls carry a `#[diagnostic::do_not_recommend]` attribute so the compiler does not suggest them in error messages, keeping the missing-field diagnostic pointed at the underlying struct. - -## Examples - -A provider that needs a value from its context expresses the need as a `HasField` bound, and the derive is what lets a concrete context satisfy it. First a provider for a greeting component, asking for a `String` field named `name`: - -```rust -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasField, -{ - fn greet(&self) { - println!("Hello, {}!", self.get_field(PhantomData)); - } -} -``` - -Then a context that derives `HasField` and wires the component to that provider: - -```rust -#[derive(HasField)] -pub struct Person { - pub name: String, -} - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} -``` - -Because `Person` derives `HasField`, it implements `HasField`, which is exactly the bound `GreetHello` requires; the wiring therefore type-checks and `person.greet()` prints the person's name. - -In practice the explicit `HasField` bound is rarely written by hand. The same `name` access is more idiomatically expressed with [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md) (`fn name(&self) -> &str`) or with an [`#[implicit]`](../attributes/implicit.md) argument (`fn greet(&self, #[implicit] name: String)`), both of which generate the `HasField` bound for you. The derive is the foundation those forms stand on. - -## Related constructs - -`#[derive(HasField)]` underpins most value-level dependency injection in CGP. [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md) turns a getter-trait method into a blanket impl backed by a `HasField` bound, and [`#[cgp_getter]`](../macros/cgp_getter.md) does the same through the [`UseField`](../providers/use_field.md) provider, which reads an arbitrary field by tag. The [`#[implicit]`](../attributes/implicit.md) argument form desugars context parameters into `get_field` calls against these impls. The tags it generates are documented in [`Symbol!`](../macros/symbol.md) (named fields) and [`Index`](../types/index.md) (tuple fields). For the aggregate view of all fields at once, see [`#[derive(HasFields)]`](derive_has_fields.md), which is commonly derived alongside this one and is itself the basis for [`#[derive(CgpData)]`](derive_cgp_data.md). - -## Source - -- Entry point: `derive_has_field` in [crates/macros/cgp-macro-lib/src/derive_has_field.rs](../../../crates/macros/cgp-macro-lib/src/derive_has_field.rs), registered as the `HasField` proc-macro derive in [crates/macros/cgp-macro/src/lib.rs](../../../crates/macros/cgp-macro/src/lib.rs). It parses the input as a `syn::ItemStruct`, wraps it in an `ItemCgpRecord` ([crates/macros/cgp-macro-core/src/types/cgp_data/record.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/record.rs)), and calls `to_has_field_impls`. -- Codegen: `derive_has_field_impls_from_struct` in [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_field.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_field.rs) — this is where named fields are mapped to `Symbol` tags and unnamed fields to `Index` tags, and where both the `HasField` and `HasFieldMut` impls are emitted. -- Traits: [crates/core/cgp-field/src/traits/has_field.rs](../../../crates/core/cgp-field/src/traits/has_field.rs) and [crates/core/cgp-field/src/traits/has_field_mut.rs](../../../crates/core/cgp-field/src/traits/has_field_mut.rs). -- Internal walkthrough (the codegen helper that synthesizes each impl, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/derive_has_field.md](../../implementation/entrypoints/derive_has_field.md). diff --git a/docs/reference/derives/derive_has_fields.md b/docs/reference/derives/derive_has_fields.md deleted file mode 100644 index 13589066..00000000 --- a/docs/reference/derives/derive_has_fields.md +++ /dev/null @@ -1,155 +0,0 @@ -# `#[derive(HasFields)]` - -`#[derive(HasFields)]` is the derive macro that gives a type a whole-shape view: it implements `HasFields` (and `HasFieldsRef`, `ToFields`, `FromFields`, `ToFieldsRef`) so that the entire struct or enum is described by a single type-level [`Product`](../macros/product.md) of named fields, or for an enum a type-level [`Sum`](../macros/sum.md) of named variants. - -## Purpose - -`#[derive(HasFields)]` exists to expose a type's complete field structure as one type, rather than one entry per field. Where [`#[derive(HasField)]`](derive_has_field.md) answers "give me *this* field by name," `HasFields` answers "describe *all* the fields at once" — it produces a single associated `Fields` type that is the type-level list of every field, each tagged with its name. This aggregate view is what lets generic code fold over a context's fields uniformly: serialization, builders, conversions, and the extensible-record machinery all operate on the `Fields` type rather than reaching for fields one at a time. - -The distinction from `HasField` is the whole point. `HasField` is indexed access — many small impls, each keyed by a tag, used for dependency injection where a provider wants one specific value. `HasFields` is the structural mirror — a single impl whose `Fields` type enumerates the entire shape, used when an algorithm must process the type as a record (for a struct) or as a tagged union (for an enum). The two are complementary, and a context that wants both indexed access and structural processing derives both. - -Alongside the structural type, the derive also generates the conversions that move values in and out of that representation. `ToFields` turns an owned value into its `Fields` product, `FromFields` rebuilds the value from a `Fields` product, and `ToFieldsRef` borrows the value as a product of references. Together these make the generic `Fields` view a two-way door: code can decompose a concrete type into its anonymous structural form, transform it generically, and reconstruct the concrete type. - -## Syntax - -The macro is applied as a derive and takes no arguments, but unlike `HasField` it accepts both structs and enums: - -```rust -#[derive(HasFields)] -pub struct Person { - pub name: String, - pub age: u8, -} - -#[derive(HasFields)] -pub enum Shape { - Circle { radius: f64 }, - Rectangle { width: f64, height: f64 }, -} -``` - -For a struct the generated `Fields` type is a product; for an enum it is a sum. Named fields within either are tagged by [`Symbol!`](../macros/symbol.md), and unnamed (tuple) fields by [`Index`](../types/index.md), exactly as in `HasField`. A single-field tuple struct (a newtype) is treated specially: its `Fields` is the inner type directly, not wrapped in a one-element product. Applying the derive to anything other than a struct or enum is a compile error. - -## Expansion - -`#[derive(HasFields)]` emits five impls — `HasFields`, `HasFieldsRef`, `ToFields`, `FromFields`, and `ToFieldsRef` — and leaves the type definition untouched. The shape of the `Fields` type is the load-bearing part. Starting from a named-field struct: - -```rust -#[derive(HasFields)] -pub struct Person { - pub name: String, - pub age: u8, -} -``` - -each field becomes a [`Field`](../types/field.md) entry — the `Value` wrapped together with its type-level name tag — and the entries are chained into a [`Product!`](../macros/product.md). The `HasFields` impl names that product, and `HasFieldsRef` names the same product with each value borrowed: - -```rust -impl HasFields for Person { - type Fields = Product![ - Field, - Field, - ]; -} - -impl HasFieldsRef for Person { - type FieldsRef<'a> = Product![ - Field, - Field, - ] - where - Self: 'a; -} -``` - -The accompanying conversions move values between `Person` and that product. `to_fields` builds the product from the struct's fields, `from_fields` destructures the product back into the struct, and `to_fields_ref` builds the borrowed product: - -```rust -impl ToFields for Person { - fn to_fields(self) -> Self::Fields { - Cons(self.name.into(), Cons(self.age.into(), Nil)) - } -} - -impl FromFields for Person { - fn from_fields(Cons(name, Cons(age, Nil)): Self::Fields) -> Self { - Self { name: name.value, age: age.value } - } -} - -impl ToFieldsRef for Person { - fn to_fields_ref<'a>(&'a self) -> Self::FieldsRef<'a> - where - Self: 'a, - { - Cons((&self.name).into(), Cons((&self.age).into(), Nil)) - } -} -``` - -An enum expands to a [`Sum`](../macros/sum.md) instead of a product. Each variant becomes an [`Either`](../types/either.md) arm tagged by the variant name with [`Symbol!`](../macros/symbol.md), carrying that variant's own fields as a nested product, and the chain is terminated by `Void`. Starting from: - -```rust -#[derive(HasFields)] -pub enum Shape { - Circle { radius: f64 }, - Rectangle { width: f64, height: f64 }, -} -``` - -the `HasFields` impl names the sum of variants: - -```rust -impl HasFields for Shape { - type Fields = Either< - Field]>, - Either< - Field, - Field, - ]>, - Void, - >, - >; -} -``` - -The enum derive likewise emits `HasFieldsRef` (the same sum with borrowed values), `ToFields`, `FromFields`, and `ToFieldsRef`, with each conversion matching on the concrete variant and mapping it to the corresponding `Either` arm. - -The associated trait definitions these impls satisfy are minimal: `HasFields` carries `type Fields`, `HasFieldsRef` carries `type FieldsRef<'a>`, and the three conversion traits each supertrait one of those and add a single method (`to_fields`, `from_fields`, `to_fields_ref`). - -## Examples - -Deriving `HasFields` lets generic code treat any context as a record without naming its concrete type. A common pairing is to derive it alongside [`HasField`](derive_has_field.md) so the same struct supports both indexed and structural access: - -```rust -use cgp::prelude::*; - -#[derive(HasField, HasFields)] -pub struct Config { - pub host: String, - pub port: u16, -} -``` - -With `HasFields` in place, `Config::Fields` is `Product![Field, Field]`, and a value can be round-tripped through that product: - -```rust -let config = Config { host: "localhost".into(), port: 8080 }; - -let fields = config.to_fields(); // ToFields → the Product -let config_again = Config::from_fields(fields); // FromFields → back to Config -``` - -Generic algorithms key off the `Fields` type rather than the concrete struct. This is how higher-level constructs such as the extensible-builder and data-manipulation machinery in [`#[derive(CgpData)]`](derive_cgp_data.md) operate over arbitrary contexts: they bound `Context: HasFields` (or `FromFields`/`ToFields`) and process `Context::Fields` structurally. - -## Related constructs - -`#[derive(HasFields)]` is the structural counterpart to [`#[derive(HasField)]`](derive_has_field.md): `HasField` gives indexed, single-field access for dependency injection, while `HasFields` gives the aggregate view of the whole type. The `Fields` type it produces is built from [`Product`](../macros/product.md) for structs and [`Sum`](../macros/sum.md) for enums, with named entries tagged by [`Symbol!`](../macros/symbol.md). [`#[derive(CgpData)]`](derive_cgp_data.md) builds on this derive, generating `HasFields` together with the additional builder, partial-record, and field-update machinery needed for extensible data. - -## Source - -- Entry point: `derive_has_fields` in [crates/macros/cgp-macro-lib/src/derive_has_fields.rs](../../../crates/macros/cgp-macro-lib/src/derive_has_fields.rs), registered as the `HasFields` proc-macro derive in [crates/macros/cgp-macro/src/lib.rs](../../../crates/macros/cgp-macro/src/lib.rs). It dispatches on the parsed item: structs go through `ItemCgpRecord::to_has_fields_impls` → `derive_has_fields_impls_from_struct`, and enums through `derive_has_fields_impls_from_enum`, both in [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/). -- Codegen: within that module, `product.rs` (`item_fields_to_product_type`) builds the struct `Fields` product, `sum.rs` (`variants_to_sum_type`) builds the enum `Fields` sum, and `from_fields_*`, `to_fields_*`, and `to_fields_ref_*` build the conversion impls. -- Traits: [crates/core/cgp-field/src/traits/has_fields.rs](../../../crates/core/cgp-field/src/traits/has_fields.rs), [crates/core/cgp-field/src/traits/to_fields.rs](../../../crates/core/cgp-field/src/traits/to_fields.rs), and [crates/core/cgp-field/src/traits/from_fields.rs](../../../crates/core/cgp-field/src/traits/from_fields.rs); the `Field`, `Either`, and `Void` building blocks live under [crates/core/cgp-field/src/types/](../../../crates/core/cgp-field/src/types/). -- Internal walkthrough (the codegen helpers that build the product and sum spines, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/derive_has_fields.md](../../implementation/entrypoints/derive_has_fields.md). diff --git a/docs/reference/macros/async_trait.md b/docs/reference/macros/async_trait.md deleted file mode 100644 index b538c636..00000000 --- a/docs/reference/macros/async_trait.md +++ /dev/null @@ -1,141 +0,0 @@ -# `#[async_trait]` - -`#[async_trait]` rewrites each `async fn` declared in a trait into an ordinary method that returns `impl Future`, so a trait can advertise async methods without tripping the `async_fn_in_trait` lint. - -## Purpose - -`#[async_trait]` exists to make async methods in traits ergonomic to declare. Writing a bare `async fn` directly inside a trait definition compiles, but the compiler emits the `async_fn_in_trait` lint, because a bare `async fn` in a trait leaves the returned future's auto-traits unnameable by callers and is easy to misuse. The hand-written alternative — declaring the method as `fn name(&self) -> impl Future` — silences the lint but is verbose and obscures the intent. `#[async_trait]` lets the author write the natural `async fn` form and performs that rewrite mechanically, so the trait reads as async code while the generated declaration is the lint-clean `impl Future` form. - -The rewrite is a zero-cost desugaring to return-position `impl Trait` in traits: no boxing, no allocation, and no implicit `Send` bound are introduced. The returned future is exactly the one the method body produces. This is why the macro is used pervasively alongside [`#[cgp_component]`](cgp_component.md) and [`#[cgp_fn]`](cgp_fn.md) whenever a CGP capability is asynchronous — it is the standard way to spell an async method in a CGP trait. - -## Syntax - -`#[async_trait]` is an attribute macro applied to a trait definition, taking no arguments. Any tokens placed in the attribute's argument position are ignored, so it is always written bare: - -```rust -#[async_trait] -pub trait CanFetchStorageObject { - async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result>; -} -``` - -Only the methods declared `async` are affected; non-async methods, associated types, and associated constants in the same trait pass through untouched. When `#[async_trait]` is stacked with a host macro, the ordering follows what that macro needs. With [`#[cgp_component]`](cgp_component.md), place `#[async_trait]` outermost (first) so it rewrites the trait before the component macro reads it: - -```rust -#[async_trait] -#[cgp_component(StorageObjectFetcher)] -pub trait CanFetchStorageObject { - async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result>; -} -``` - -With [`#[cgp_fn]`](cgp_fn.md), which generates the trait from a function, `#[async_trait]` is written *below* `#[cgp_fn]` on the `async fn`; `#[cgp_fn]` copies it onto the trait and impl it generates: - -```rust -#[cgp_fn] -#[async_trait] -pub async fn fetch_storage_object( - &self, - #[implicit] storage_client: &Client, - object_id: &str, -) -> anyhow::Result> { - /* ... */ -} -``` - -## Expansion - -For each method in the trait whose signature is `async`, `#[async_trait]` removes the `async` keyword and replaces the return type `T` with `impl ::core::future::Future`. A method written without an explicit return type is treated as returning `()`. The trait above expands to: - -```rust -pub trait CanFetchStorageObject { - fn fetch_storage_object( - &self, - object_id: &str, - ) -> impl ::core::future::Future>>; -} -``` - -A method with no return arrow, such as `async fn run(&self);`, expands with `()` as the future's output: - -```rust -// async fn run(&self); -fn run(&self) -> impl ::core::future::Future; -``` - -The macro rewrites only trait definitions. When it is applied to any other item — most importantly an `impl` block — it leaves the tokens completely unchanged. This passthrough is what makes the macro compose so cleanly: an `async fn` is already legal in an inherent or trait `impl` on stable Rust, and only a trait *declaration* triggers the `async_fn_in_trait` lint. So a provider implementation can keep the natural `async fn` body while the matching trait declaration carries the rewritten `-> impl Future` signature, and the two still agree because an `async fn` desugars to exactly such a future-returning method. - -This composition is visible in how [`#[cgp_fn]`](cgp_fn.md) uses it. Given an async `#[cgp_fn]` carrying `#[async_trait]`, `#[cgp_fn]` first produces a trait and a blanket impl, attaching `#[async_trait]` to both: - -```rust -#[async_trait] -pub trait FetchStorageObject { - async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result>; -} - -#[async_trait] -impl<__Context__> FetchStorageObject for __Context__ -where - Self: HasField, -{ - async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result> { - let storage_client: &Client = - self.get_field(PhantomData::); - /* ... */ - } -} -``` - -`#[async_trait]` then runs on each. On the trait it rewrites the declaration to `fn fetch_storage_object(&self, object_id: &str) -> impl ::core::future::Future>>`. On the impl it is a no-op, so the `async fn` body remains as written — and that `async fn` satisfies the trait's `impl Future` method. - -## Examples - -The most common use is declaring an asynchronous CGP component. The consumer trait carries `#[async_trait]` so its method is a clean `impl Future` declaration, and each provider implements it with a natural `async fn` body: - -```rust -use cgp::prelude::*; - -#[async_trait] -#[cgp_component(StorageObjectFetcher)] -pub trait CanFetchStorageObject { - async fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result>; -} - -#[cgp_impl(new FetchS3Object)] -impl StorageObjectFetcher { - async fn fetch_storage_object( - &self, - #[implicit] storage_client: &Client, - #[implicit] bucket_id: &str, - object_id: &str, - ) -> anyhow::Result> { - let output = storage_client - .get_object() - .bucket(bucket_id) - .key(object_id) - .send() - .await?; - - Ok(output.body.collect().await?.into_bytes().to_vec()) - } -} -``` - -Defining the same capability as a single implementation with [`#[cgp_fn]`](cgp_fn.md) needs `#[async_trait]` directly below it, as shown in the Syntax section. In both forms the author writes only `async fn`, and the lint-clean `impl Future` declaration is generated. - -## Related constructs - -`#[async_trait]` is most often stacked with [`#[cgp_component]`](cgp_component.md), which builds the consumer and provider traits for an async capability, and with [`#[cgp_fn]`](cgp_fn.md), which generates an async trait and blanket impl from a function. Providers for an async component are written with [`#[cgp_impl]`](cgp_impl.md) using ordinary `async fn` bodies, since the macro's passthrough on impl blocks leaves those untouched. It is independent of CGP's wiring layer — [`delegate_components!`](delegate_components.md) and [`check_components!`](check_components.md) treat an async component exactly like a synchronous one. - -## Known issues - -`#[async_trait]` rewrites only the method signature, never the method body, so an async trait method that carries a *default body* is mishandled. The macro strips `async` from such a method's signature and changes its return type to `impl Future`, but the body is left verbatim — it is not wrapped in an `async { ... }` block. The result is a non-async method whose body still returns a plain value and may use `.await`, which fails to compile. In practice this is rarely hit, because async methods in a trait are almost always declarations without bodies, and provided async behavior is supplied by a provider impl instead; but a default-bodied `async fn` inside a `#[async_trait]` trait is not supported. - -The generated future carries no `Send` bound. Because the rewrite produces a bare `impl Future`, the returned future is `Send` only when the concrete future happens to be, and the trait does not require it. Code that must spawn the future onto a work-stealing, multi-threaded executor — which demands `Send` futures — cannot express that requirement through this macro and must add the bound by other means, as described in [recovering `Send` bounds](../../concepts/send-bounds.md). - -## Source - -- Entry point: `async_trait` in [crates/macros/cgp-async-macro/src/lib.rs](../../../crates/macros/cgp-async-macro/src/lib.rs), a `#[proc_macro_attribute]` that discards its attribute arguments and forwards the annotated item to `impl_async`. -- Rewrite: [crates/macros/cgp-async-macro/src/impl_async.rs](../../../crates/macros/cgp-async-macro/src/impl_async.rs) — it parses the item as a `syn::ItemTrait`, and on success walks the trait's methods, replacing each `async` signature's output with `-> impl ::core::future::Future` and clearing the `async` keyword; if the item does not parse as a trait, it is returned unchanged. -- Prelude re-export: [crates/main/cgp-core/src/prelude.rs](../../../crates/main/cgp-core/src/prelude.rs), so `use cgp::prelude::*;` brings it into scope. -- Internal walkthrough (the parse-or-passthrough structure, the signature rewrite, the default-body limitation, and the index of tests): [implementation/entrypoints/async_trait.md](../../implementation/entrypoints/async_trait.md). diff --git a/docs/reference/macros/blanket_trait.md b/docs/reference/macros/blanket_trait.md deleted file mode 100644 index 6ec2d28c..00000000 --- a/docs/reference/macros/blanket_trait.md +++ /dev/null @@ -1,173 +0,0 @@ -# `#[blanket_trait]` - -`#[blanket_trait]` generates a blanket implementation for a trait whose methods, associated types, and constants carry default definitions, turning the trait into an extension trait that hides its supertrait constraints behind a clean interface. - -## Purpose - -`#[blanket_trait]` exists to remove the boilerplate of writing the blanket impl that makes an extension trait work. The blanket-trait pattern — the foundation CGP itself is built on — uses the `where` clause of a blanket impl to hide the constraints a piece of generic code needs from the interface that callers see. Written by hand, the pattern is repetitive: you state the supertrait bounds twice, once on the trait and once on the impl, and you copy each default method body from the trait into the impl. `#[blanket_trait]` lets you write the trait once, with its default bodies, and generates the matching impl for you. - -The payoff is the impl-side-dependency, or dependency-injection, pattern in its purest form. A trait declared `pub trait FooBar: Foo + Bar` with a default `foo_bar` method exposes only `foo_bar` to its callers; the `Foo + Bar` requirements are carried by the generated blanket impl's `where` clause, so any context satisfying `Foo` and `Bar` automatically gains `FooBar` without naming those dependencies at the call site. This is the same constraint-hiding that the `/cgp` skill introduces as the core idea behind blanket traits, and it is preferred over a free generic function because transitive callers never have to repeat the `Foo + Bar` bounds. - -`#[blanket_trait]` is not a CGP component. It produces an ordinary Rust trait and an ordinary blanket impl, with no consumer/provider split, no component name, and no wiring. It is the tool to reach for when a capability has exactly one definition and you want the extension-trait ergonomics without committing to the full CGP component machinery. When a capability later needs multiple alternative implementations, the trait can be promoted to a [`#[cgp_component]`](cgp_component.md). - -## Syntax - -`#[blanket_trait]` is applied as an attribute on a trait definition. Every method and constant in the trait must have a default body, and every associated type may declare bounds; these defaults are what the generated impl forwards. The supertraits of the trait become the dependencies that the blanket impl requires. - -```rust -#[blanket_trait] -pub trait FooBar: Foo + Bar { - fn foo_bar(&self) { - self.foo(); - self.bar(); - } -} -``` - -The macro accepts an optional argument naming the generic context type used in the generated impl. When omitted, it defaults to `__Context__` — the same reserved identifier used across the CGP macros, chosen to avoid colliding with the user's own type parameters. Passing an identifier overrides this name, which is occasionally useful for readability in expanded output. - -```rust -#[blanket_trait(Ctx)] -pub trait FooBar: Foo + Bar { /* ... */ } -``` - -The trait may carry generic parameters and associated types. Generic parameters on the trait are copied onto the impl. Associated types are turned into fresh generic parameters on the impl and bound through the supertrait's associated-type equality, which is how the pattern lifts an associated type out of a supertrait — covered in the expansion below. - -## Syntax Grammar - -The attribute argument of `#[blanket_trait]` is a single optional context name: - -```ebnf -BlanketTraitArgs -> ContextName? - -ContextName -> IDENTIFIER -``` - -When the argument is omitted, the generic context type in the generated impl defaults to the reserved identifier `__Context__`. A given `IDENTIFIER` overrides that name. - -## Expansion - -`#[blanket_trait]` emits two items: the trait, unchanged from its definition, and a blanket impl for a generic context. The impl forwards each default method body, requires the trait's supertraits in its `where` clause, and strips the defaults from the trait so the trait declaration stays a pure interface. Starting from the method example: - -```rust -#[blanket_trait] -pub trait FooBar: Foo + Bar { - fn foo_bar(&self) { - self.foo(); - self.bar(); - } -} -``` - -the macro produces the trait and a blanket impl whose `where` clause carries the `Foo + Bar` supertraits as the hidden dependency: - -```rust -pub trait FooBar: Foo + Bar { - fn foo_bar(&self); -} - -impl<__Context__> FooBar for __Context__ -where - __Context__: Foo + Bar, -{ - fn foo_bar(&self) { - self.foo(); - self.bar(); - } -} -``` - -The simplest case, a trait with no methods, generates an empty impl — a trait alias in everything but name: - -```rust -#[blanket_trait] -pub trait FooBar: Foo + Bar {} -``` - -expands to: - -```rust -pub trait FooBar: Foo + Bar {} - -impl<__Context__> FooBar for __Context__ -where - __Context__: Foo + Bar, -{} -``` - -Associated types receive special handling, because lifting an associated type out of a supertrait is one of the pattern's main uses. Each associated type in the trait becomes a new generic parameter on the impl, the supertrait's associated-type equality is rewritten to reference that parameter, and the impl assigns the parameter back to the associated type. Given: - -```rust -#[blanket_trait] -pub trait HasFooTypeAtBar: HasFooTypeAt { - type FooBar; -} -``` - -the macro emits: - -```rust -pub trait HasFooTypeAtBar: HasFooTypeAt { - type FooBar; -} - -impl<__Context__, FooBar> HasFooTypeAtBar for __Context__ -where - __Context__: HasFooTypeAt, -{ - type FooBar = FooBar; -} -``` - -When the associated type declares bounds, those bounds are moved into the impl's `where` clause as predicates on the introduced parameter. Declaring `type FooBar: Clone` adds `FooBar: Clone` to the generated `where` clause alongside the supertrait requirement. Associated constants are forwarded in the same way as methods: their default expressions become the impl's constant definitions, and the trait keeps only the declaration. A method or constant without a usable default is an error, since the macro has no body to forward; associated types need no default, because the macro supplies the assignment (`type FooBar = FooBar;`) itself from the introduced parameter. - -## Examples - -A self-contained extension trait that hides two dependencies behind one method illustrates the everyday use. The `FooBar` capability is defined once, with its body, and applies to any context implementing both `Foo` and `Bar`: - -```rust -use cgp::prelude::*; - -pub trait Foo { - fn foo(&self); -} - -pub trait Bar { - fn bar(&self); -} - -#[blanket_trait] -pub trait FooBar: Foo + Bar { - fn foo_bar(&self) { - self.foo(); - self.bar(); - } -} - -pub struct Context; - -impl Foo for Context { - fn foo(&self) {} -} - -impl Bar for Context { - fn bar(&self) {} -} - -fn run(ctx: &Context) { - ctx.foo_bar(); // available because Context: Foo + Bar -} -``` - -`Context` never mentions `FooBar` directly; it implements only `Foo` and `Bar`, and the generated blanket impl supplies `foo_bar` automatically. A caller of `foo_bar` sees a single-method interface and is shielded from the `Foo + Bar` requirement entirely. - -## Related constructs - -`#[blanket_trait]` and [`#[cgp_fn]`](cgp_fn.md) both produce a blanket impl over a generic context, and both are single-implementation tools that need no wiring; the difference is the input. `#[cgp_fn]` derives the trait and its body from a plain function with `#[implicit]` field arguments, while `#[blanket_trait]` takes a trait with default bodies and supertrait constraints directly, making it the better fit when the dependencies are themselves traits rather than context fields. Both contrast with [`#[cgp_component]`](cgp_component.md), which produces a consumer/provider pair supporting multiple implementations selected through [`delegate_components!`](delegate_components.md); a `#[blanket_trait]` can be promoted to a `#[cgp_component]` when more than one implementation becomes necessary. The constraint-hiding it performs is the same impl-side-dependency mechanism that [`#[cgp_auto_getter]`](cgp_auto_getter.md) and [`HasField`](../traits/has_field.md) rely on for value injection. - -## Source - -- Entry point: `blanket_trait` in [crates/macros/cgp-macro-lib/src/blanket_trait.rs](../../../crates/macros/cgp-macro-lib/src/blanket_trait.rs), which parses the optional context identifier (defaulting to `__Context__` when the attribute argument is empty) and the trait, then runs `item.to_items()?`. -- Logic: [crates/macros/cgp-macro-core/src/types/blanket_trait.rs](../../../crates/macros/cgp-macro-core/src/types/blanket_trait.rs) — `to_item_impl` walks the trait items, forwards each default method/const body and associated-type assignment into the impl, lifts associated types into impl generics, moves associated-type bounds into the `where` clause, and appends the trait's supertraits as the `__Context__: ...` predicate. -- `Self`-to-parameter rewriting for associated types: `RemoveSelfPathVisitor` in [crates/macros/cgp-macro-core/src/visitors/](../../../crates/macros/cgp-macro-core/src/visitors/). -- Internal walkthrough (the codegen walk, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/blanket_trait.md](../../implementation/entrypoints/blanket_trait.md). diff --git a/docs/reference/macros/cgp_auto_dispatch.md b/docs/reference/macros/cgp_auto_dispatch.md deleted file mode 100644 index 53eafa4a..00000000 --- a/docs/reference/macros/cgp_auto_dispatch.md +++ /dev/null @@ -1,140 +0,0 @@ -# `#[cgp_auto_dispatch]` - -`#[cgp_auto_dispatch]` is an attribute macro that, given a trait implemented separately for each payload type, generates a blanket implementation of that trait for an enum by matching the enum's current variant and delegating to the implementation for that variant's payload. - -## Purpose - -`#[cgp_auto_dispatch]` solves the common case of "I have a trait with one impl per type, and I want it to work on an enum of those types too." Without it, a programmer would write a `match` arm per variant by hand, or wire up the [dispatch combinators](../providers/dispatch_combinators.md) — a matcher, a per-variant computer, and the field-handler machinery — manually. The macro removes that boilerplate: it inspects the trait's methods and emits both the per-variant handler each method needs and the enum-level blanket impl that runs the matcher, so the only code the programmer writes is the trait, its per-payload impls, and the derive that makes the enum extensible. - -The macro is the highest-level entry point to the [dispatching](../../concepts/dispatching.md) pattern. It is meant for the frequent situation where the per-variant behavior is exactly "call the same trait method on the payload," and it generates the same matcher wiring described in [`dispatch_combinators`](../providers/dispatch_combinators.md) so that the generated impl behaves identically to a hand-written one. When the per-variant behavior is more elaborate, or the dispatch needs to be wired into a context's components rather than implemented directly on the enum, the underlying combinators are used directly instead. - -## Syntax - -`#[cgp_auto_dispatch]` is written above a trait definition and takes no arguments. The trait may have generic parameters and supertraits, and each method may take `self` by value, by shared reference, or by mutable reference, may take additional value or reference arguments, and may be `async`: - -```rust -#[cgp_auto_dispatch] -pub trait HasArea { - fn area(&self) -> f64; -} -``` - -Two restrictions apply, both enforced at expansion time. A trait method may not have non-lifetime generic parameters, because Rust lacks the quantified trait bounds the generated impl would need; lifetime parameters on a method are allowed. Every trait item must be a method — associated types and constants are rejected. Each method must have a `self` receiver, since the receiver is the enum value being matched. - -## Expansion - -The macro keeps the original trait unchanged and appends two kinds of generated code: one blanket impl of the trait for a fresh enum type parameter named `__Variants__`, and, for each method, one free function turned into a per-variant computer by [`#[cgp_computer]`](cgp_computer.md). Taking the `HasArea` trait above, the macro emits a per-variant computer for the `area` method: - -```rust -#[cgp_computer(ComputeArea)] -fn area<'__a__, __Variants__: HasArea>(__Variants__: &'__a__ __Variants__) -> f64 { - __Variants__.area() -} -``` - -The computer's name is `Compute` followed by the method name in PascalCase, so `area` yields `ComputeArea`. Its body simply calls the trait method on the payload, which means the per-variant handler is "invoke `HasArea::area` on whatever payload type this variant holds." The function is bound by `__Variants__: HasArea` so it applies to every payload type that implements the trait, and it borrows the payload by a fresh lifetime `'__a__` to mirror the `&self` receiver. - -The macro then emits the enum-level blanket impl, which wires a matcher over `__Variants__`: - -```rust -impl<__Variants__> HasArea for __Variants__ -where - MatchWithValueHandlersRef: - for<'__a__> Computer<(), (), &'__a__ __Variants__, Output = f64>, - __Variants__: HasExtractor, -{ - fn area(&self) -> f64 { - MatchWithValueHandlersRef::::compute( - &(), - ::core::marker::PhantomData::<()>, - self, - ) - } -} -``` - -The matcher struct the impl picks depends on the method's receiver and arguments, and every choice is from the value-handler matcher family so that the per-variant computer receives the bare payload. A method whose receiver and argument list determine the selection as follows: a `&self` method with no extra arguments uses `MatchWithValueHandlersRef`, a `&mut self` method with no extra arguments uses `MatchWithValueHandlersMut`, and a by-value `self` method with no extra arguments uses `MatchWithValueHandlers`. When the method takes additional arguments, the matcher switches to the first-argument family — `MatchFirstWithValueHandlersRef`, `MatchFirstWithValueHandlersMut`, or `MatchFirstWithValueHandlers` respectively — and the arguments are bundled into the matcher input as a tuple. The matcher is invoked with a unit context `&()` and unit code `PhantomData::<()>`, since the per-variant logic depends only on the payload, not on any surrounding context. - -A method that takes arguments shows the first-argument form. For a `contains(&self, x: f64, y: f64) -> bool` method, the generated impl bundles the receiver and the arguments into the input tuple and selects `MatchFirstWithValueHandlersRef`: - -```rust -// where MatchFirstWithValueHandlersRef: -// for<'__a__> Computer<(), (), (&'__a__ __Variants__, (f64, f64)), Output = bool> -fn contains(&self, arg_0: f64, arg_1: f64) -> bool { - MatchFirstWithValueHandlersRef::::compute( - &(), - ::core::marker::PhantomData::<()>, - (self, (arg_0, arg_1)), - ) -} -``` - -For an `async` method the macro selects the `AsyncComputer` form of the bound and the matcher call instead of `Computer`, appends the method's lifetime handling for any reference arguments or reference return type by introducing the `'__a__` lifetime and a `for<'__a__>` quantifier where needed, and awaits the matcher result. The enum-level impl always carries the `__Variants__: HasExtractor` bound, since matching requires the enum to be extensible. - -## Examples - -The macro turns a per-type trait into one that also works on an enum of those types, with no hand-written matching. The enum derives `CgpData` so it is extensible, the trait carries `#[cgp_auto_dispatch]`, and each payload type implements the trait on its own: - -```rust -use cgp::prelude::*; - -#[derive(CgpData)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} - -pub struct Circle { pub radius: f64 } -pub struct Rectangle { pub width: f64, pub height: f64 } - -#[cgp_auto_dispatch] -pub trait HasArea { - fn area(&self) -> f64; -} - -impl HasArea for Circle { - fn area(&self) -> f64 { core::f64::consts::PI * self.radius * self.radius } -} - -impl HasArea for Rectangle { - fn area(&self) -> f64 { self.width * self.height } -} - -// HasArea is now also implemented for Shape, dispatching to the variant's impl: -let shape = Shape::Rectangle(Rectangle { width: 2.0, height: 2.0 }); -assert_eq!(shape.area(), 4.0); -``` - -Because the generated impl is bound by `MatchWithValueHandlersRef: …`, it requires every variant's payload to implement `HasArea`; forgetting an impl for one variant is a compile error at the point the enum's `area` is used. A `&mut self` method dispatches the same way through the `Mut` matcher, so a single `#[cgp_auto_dispatch]` trait can mix reading and mutating methods: - -```rust -#[cgp_auto_dispatch] -pub trait CanScale { - fn scale(&mut self, factor: f64); -} - -impl CanScale for Circle { - fn scale(&mut self, factor: f64) { self.radius *= factor; } -} - -impl CanScale for Rectangle { - fn scale(&mut self, factor: f64) { self.width *= factor; self.height *= factor; } -} - -let mut shape = Shape::Rectangle(Rectangle { width: 2.0, height: 2.0 }); -shape.scale(2.0); // dispatches to Rectangle::scale through MatchFirstWithValueHandlersMut -``` - -## Known issues - -The macro rejects trait methods with non-lifetime generic parameters, so a dispatch trait cannot have a generic method even though an ordinary trait can. This is a deliberate limitation rather than an oversight: the generated blanket impl would need a quantified trait bound over the method's type parameter to guarantee every variant's payload satisfies the bound for all instantiations, and Rust has no such bound. A method that needs to be generic must be handled with the dispatch combinators directly instead of through this macro. - -## Related constructs - -`#[cgp_auto_dispatch]` is the automated front end to the [dispatching](../../concepts/dispatching.md) pattern, and the providers it wires together are documented in [`dispatch_combinators`](../providers/dispatch_combinators.md) — specifically the value-handler matcher family (`MatchWithValueHandlers` and its `Ref`/`Mut` and `First` variants). It emits each per-variant handler with [`#[cgp_computer]`](cgp_computer.md), producing a [`Computer`](../components/computer.md) provider. The enum it dispatches over must be made extensible with a `CgpData`-style derive that supplies [`HasExtractor`](../traits/extract_field.md) and [`HasFields`](../traits/has_fields.md). For per-variant behavior that is not a direct method call, or for dispatch wired into a context's components rather than implemented on the enum, the combinators in [`dispatch_combinators`](../providers/dispatch_combinators.md) are used directly. The [extensible shapes](../../examples/extensible-shapes.md) example develops this macro end to end — dispatching an `area` reader, a mutating `scale`, and argument-taking methods over an enum of shapes — and shows how the generated wiring relates to using the combinators directly. - -## Source - -- Entry point: `cgp_auto_dispatch` in [crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs), forwarded from the proc-macro shim in [crates/macros/cgp-extra-macro/src/lib.rs](../../../crates/macros/cgp-extra-macro/src/lib.rs) and re-exported through [crates/main/cgp-extra/src/prelude.rs](../../../crates/main/cgp-extra/src/prelude.rs). -- Matchers it generates: [crates/extra/cgp-dispatch/src/providers/matchers/](../../../crates/extra/cgp-dispatch/src/providers/matchers/). -- Internal walkthrough (the blanket-impl and per-variant-computer helpers, the matcher selection, the lifetime elaboration, and the index of behavioral tests): [implementation/entrypoints/cgp_auto_dispatch.md](../../implementation/entrypoints/cgp_auto_dispatch.md). diff --git a/docs/reference/macros/cgp_auto_getter.md b/docs/reference/macros/cgp_auto_getter.md deleted file mode 100644 index 03a5aaf2..00000000 --- a/docs/reference/macros/cgp_auto_getter.md +++ /dev/null @@ -1,166 +0,0 @@ -# `#[cgp_auto_getter]` - -`#[cgp_auto_getter]` turns a getter trait into a single blanket implementation that satisfies each getter method by reading a context field through [`HasField`](../traits/has_field.md), keyed by the method's own name as a [`Symbol!`](symbol.md). - -## Purpose - -`#[cgp_auto_getter]` exists to publish a context field as a reusable getter *capability* — a named `self.name()` accessor that other providers depend on. Reading a value out of a context is the most basic form of dependency injection in CGP, and the underlying mechanism — `HasField` — is precise but verbose and unfamiliar to most Rust developers. The macro lets you state the intent in ordinary trait-method syntax (`fn name(&self) -> &str;`) and generates the `HasField`-based plumbing for you. - -Prefer an [`#[implicit]`](../attributes/implicit.md) argument over `#[cgp_auto_getter]`, and reach for the getter trait only when an implicit argument genuinely cannot do the job. For the ordinary case — a provider reading a field from its own context — an implicit argument injects the value as an ordinary-looking parameter with no separate trait to declare, and it does so with the same `HasField` bound and the same access rules a getter would use, so the getter trait adds a declaration without buying anything. Because an implicit argument reads only from the provider's own context (`self`), and reads a plain `&T` argument by reference without cloning, it covers the common read directly, including a field shared by several providers that each declare the same implicit argument. - -A getter trait earns its keep in the cases an implicit argument cannot reach. The sharpest one is a field that lives on a type *other* than the provider's own context: a getter such as `HasBasicAuthHeader` reads from a request or payload type and is required as a `where` bound on that type (`Request: HasBasicAuthHeader`), so there is no `self` field for an implicit argument to read. The other cases are an accessor that must exist as a *named capability* other code depends on through [`#[uses]`](../attributes/uses.md) or a supertrait, and a getter that carries an *associated type inferred from the field* so the type stays abstract for callers to name. Use `#[cgp_auto_getter]` sparingly, for these situations; reach for an implicit argument everywhere else. - -The defining trade-off of `#[cgp_auto_getter]` is that it produces exactly one implementation and no CGP wiring. Unlike a full component, there is no provider trait, no component name, and no delegation table; the macro emits a blanket impl over a generic context, and any context that derives `HasField` with a matching field automatically satisfies the trait. This makes it the lightest-weight getter construct — ideal when the field name in the context always matches the method name and you never need an alternative implementation. - -The cost of that simplicity is rigidity. Because the field tag is derived directly from the method name, the context *must* expose a field of exactly that name. When you need the field name to differ from the method name, or you want the getter to be swappable through wiring, [`#[cgp_getter]`](cgp_getter.md) builds the same convenience on top of a real CGP component — but that is an advanced tool reserved for when a context needs full control over which field a getter reads from, and most getters do not. So among the getter constructs, `#[cgp_auto_getter]` is the default and `#[cgp_getter]` the advanced fallback for field-name decoupling — but a getter trait at all is the sparing choice, taken only when an implicit argument cannot reach the field, as the Purpose section describes. - -## Syntax - -The macro is applied as a bare attribute on a trait definition and takes no arguments. The trait body consists of getter methods, each taking `&self` (or `&mut self`) and returning a reference: - -```rust -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} -``` - -A getter trait may declare several methods, and each maps independently to its own field. Each method name becomes the field tag, so the following injects two separate fields: - -```rust -#[cgp_auto_getter] -pub trait HasDimensions { - fn width(&self) -> &f64; - fn height(&self) -> &f64; -} -``` - -The return type controls how the field value is read, and several shorthand forms are recognized so the method signature stays ergonomic. A plain reference `&T` reads a field of type `T` directly. The form `&str` is treated specially: it reads a `String` field and calls `.as_str()` on it, so you can return `&str` while the context stores a `String`. Other recognized forms include `Option<&T>` (an `Option` field returned via `.as_ref()`), `Option<&str>` (an `Option` field returned via `.as_deref()`, composing the `&str` special case with the option case), `&[T]` (a field whose value implements `AsRef<[T]>`), and any owned type — a path type, a tuple, or an array — read by reference and `.clone()`d. A `&mut self` receiver reads the field mutably through `get_field_mut`, and each reference form has a mutable mirror: a `&mut T` return, a `&mut [T]` return (a field implementing `AsMut<[T]>`, read via `.as_mut()`), an `Option<&mut T>` return (via `.as_mut()`), and an `Option<&mut str>` return (via `.as_deref_mut()`). - -A getter trait may also declare a single associated type and use it as the method's return type, which lets the abstract type be inferred from the field. This is covered under Expansion below. When an associated type is present, the trait must contain exactly one getter method, and that method's return type must be `&Self::AssocType`. - -## Expansion - -`#[cgp_auto_getter]` re-emits the trait unchanged and adds one blanket impl over a generic context. Starting from this input: - -```rust -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} -``` - -the macro produces the trait verbatim plus the following blanket impl. The context type parameter is the reserved name `__Context__`, the field tag is the method name rendered as a `Symbol!`, and because the return type is `&str`, the `Value` is `String` and the method appends `.as_str()`: - -```rust -pub trait HasName { - fn name(&self) -> &str; -} - -impl<__Context__> HasName for __Context__ -where - __Context__: HasField, -{ - fn name(&self) -> &str { - self.get_field(PhantomData::).as_str() - } -} -``` - -A trait with multiple getter methods produces one `where` predicate and one method body per field, all within the same blanket impl. The `&f64` returns below are plain references, so each `Value` matches the return type and no conversion is appended: - -```rust -impl<__Context__> HasDimensions for __Context__ -where - __Context__: HasField, - __Context__: HasField, -{ - fn width(&self) -> &f64 { - self.get_field(PhantomData::) - } - - fn height(&self) -> &f64 { - self.get_field(PhantomData::) - } -} -``` - -When the trait declares an associated type used as the return type, the macro lifts that type into an extra generic parameter on the impl and binds it through the `HasField` `Value`. This is what allows the abstract type to be inferred from the concrete field. Given: - -```rust -#[cgp_auto_getter] -pub trait HasName { - type Name: Display; - - fn name(&self) -> &Self::Name; -} -``` - -the blanket impl carries `Name` as a generic parameter, copies the associated type's bounds into the `where` clause, and sets the associated type to that parameter: - -```rust -impl<__Context__, Name> HasName for __Context__ -where - Name: Display, - __Context__: HasField, -{ - type Name = Name; - - fn name(&self) -> &Self::Name { - self.get_field(PhantomData::) - } -} -``` - -These desugarings are the exact shape the macro emits today; the only cosmetic difference from the snapshots is that `Symbol!("name")` is shown here in its sugared form rather than the expanded `Symbol<4, Chars<'n', ...>>`. - -## Examples - -A complete use pairs the getter trait with a context that derives `HasField`, after which the method is available with no further wiring: - -```rust -use cgp::prelude::*; - -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[derive(HasField)] -pub struct Person { - pub name: String, -} - -fn greet(person: &Person) { - println!("Hello, {}!", person.name()); // HasName, via the blanket impl -} -``` - -`Person` derives `HasField`, which is precisely the bound the blanket impl requires, so `Person` implements `HasName` automatically and `person.name()` returns the `name` field as a `&str`. - -A getter trait can always be implemented explicitly instead, which is useful when the context does not derive `HasField` or does not store the value under the matching field name. Because `#[cgp_auto_getter]` only adds a blanket impl, you may write the impl by hand on a concrete type: - -```rust -pub struct Person { - pub full_name: String, -} - -impl HasName for Person { - fn name(&self) -> &str { - &self.full_name - } -} -``` - -The explicit form is more verbose but requires no understanding of `HasField` or blanket impls, and it demonstrates that an auto-getter trait is an ordinary Rust trait — the macro's only job is to save you from writing the boilerplate body. - -## Related constructs - -`#[cgp_auto_getter]` is the blanket-impl counterpart to [`#[cgp_getter]`](cgp_getter.md): both read context fields through `HasField`, but `#[cgp_getter]` produces a full CGP component that can be wired to a [`UseField`](../providers/use_field.md) provider, allowing the field name to differ from the method name and the getter to be swapped per context. It builds directly on [`#[derive(HasField)]`](../derives/derive_has_field.md), which generates the per-field `HasField` impls keyed by [`Symbol!`](symbol.md). For field access inside a method body rather than through a dedicated trait, the [`#[implicit]`](../attributes/implicit.md) argument attribute follows the same field-reading semantics. When the only purpose of a getter's associated type is to serve as its return type, the associated-type form here overlaps with abstract-type components defined by [`#[cgp_type]`](cgp_type.md). - -## Source - -- Entry point: `cgp_auto_getter` in [crates/macros/cgp-macro-lib/src/cgp_auto_getter.rs](../../../crates/macros/cgp-macro-lib/src/cgp_auto_getter.rs), which rejects any attribute argument and runs `ItemCgpAutoGetter::preprocess(...).to_items()`. -- Logic: [crates/macros/cgp-macro-core/src/types/cgp_auto_getter/](../../../crates/macros/cgp-macro-core/src/types/cgp_auto_getter/) — `item.rs` sets the `__Context__` context identifier and drives field parsing, and `blanket.rs` builds the single blanket impl. -- Getter parsing (including the `&str`/`Option<&T>`/`&[T]`/owned shorthands and the associated-type rules), shared with `#[cgp_getter]`: [crates/macros/cgp-macro-core/src/functions/getter/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/getter/parse.rs), [crates/macros/cgp-macro-core/src/functions/field/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/field/parse.rs), and [crates/macros/cgp-macro-core/src/types/getter/](../../../crates/macros/cgp-macro-core/src/types/getter/). -- Internal walkthrough (the pipeline stages, the function that synthesizes each generated item, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/cgp_auto_getter.md](../../implementation/entrypoints/cgp_auto_getter.md). diff --git a/docs/reference/macros/cgp_component.md b/docs/reference/macros/cgp_component.md deleted file mode 100644 index d25e06db..00000000 --- a/docs/reference/macros/cgp_component.md +++ /dev/null @@ -1,193 +0,0 @@ -# `#[cgp_component]` - -`#[cgp_component]` is the foundational CGP macro: applied to a trait, it turns that ordinary Rust trait into a full CGP component — a consumer trait, a matching provider trait, and the blanket implementations that let a context delegate the trait's behavior to a freely chosen provider. - -## Purpose - -`#[cgp_component]` exists to lift a single trait into a pair of traits that separate *using* a capability from *implementing* it. A normal Rust trait conflates the two: the type that implements `Area` is the same type that callers invoke `.area()` on, and Rust's coherence rules then allow only one implementation per type. CGP breaks this conflation by generating two traits from one definition. The **consumer trait** is what callers use (`context.area()`); the **provider trait** is what implementers write, with the original `Self` moved into an explicit `Context` type parameter so that any number of provider types can implement it without colliding. - -The payoff is that providers become first-class, named, swappable units. Because a provider implements the provider trait for a generic `Context` rather than for itself, the usual orphan and overlap restrictions do not bite, and a crate can define many alternative providers for the same component. A concrete context then picks one provider per component through wiring (see [`delegate_components!`](delegate_components.md)), and the generated blanket impls route the consumer-trait call through that choice. `#[cgp_component]` is what makes a trait participate in this mechanism; without it, a trait is just a vanilla Rust trait. - -## Syntax - -The macro is applied as an attribute on a trait definition and takes the provider trait's name as its argument. The simplest form passes a bare identifier: - -```rust -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} -``` - -Here `CanCalculateArea` is the consumer trait (named in verb form, `CanDoSomething`) and `AreaCalculator` is the provider trait (named in noun form). When more control is needed, the macro accepts a key/value form instead of a bare identifier: - -```rust -#[cgp_component { - name: AreaCalculatorComponent, - provider: AreaCalculator, - context: Context, -}] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} -``` - -The three keys correspond to the three names the macro needs, and each has a default. The `provider` key sets the provider trait name and is the only required value; passing a bare identifier is shorthand for setting `provider` alone. The `name` key sets the component name type and defaults to the provider name with a `Component` suffix, so `AreaCalculator` yields `AreaCalculatorComponent`. The `context` key sets the identifier used for the generic context type parameter in the generated provider trait and defaults to `__Context__` — a deliberately unusual name chosen to avoid clashing with the user's own type parameters. - -Two companion attributes extend the macro for special cases and are documented separately. Adding [`#[derive_delegate(...)]`](../attributes/derive_delegate.md) generates `UseDelegate` providers that dispatch on a generic parameter, and adding [`#[extend(...)]`](../attributes/extend.md) adds supertrait bounds to the generated consumer trait. The related macros [`#[cgp_type]`](cgp_type.md) and [`#[cgp_getter]`](cgp_getter.md) build on `#[cgp_component]` to derive additional constructs for abstract-type and getter components respectively. - -When a component's supertrait exists only to supply an abstract type the trait's own signatures name — most commonly [`HasErrorType`](../components/has_error_type.md), whose `Error` a fallible method returns — prefer importing that type with [`#[use_type]`](../attributes/use_type.md) over writing the supertrait and the qualified `Self::` path by hand. Annotating the trait with `#[use_type(HasErrorType.Error)]` adds `HasErrorType` as a supertrait *and* rewrites a bare `Error` in the signatures to `::Error`, so the definition reads `pub trait CanLoad { fn load(&self, path: &str) -> Result; }` rather than spelling `: HasErrorType` and `Self::Error`. For a supertrait `#[use_type]` cannot express — a capability supertrait with no associated type to import, or a bound the trait does not name in its signatures — declare it with `#[extend(...)]` in preference to native `: Supertrait` syntax, which reads as OOP-style inheritance rather than the capability import a CGP supertrait actually is. A local associated type the trait declares itself, such as a handler's `type Output`, always stays written as `Self::Output`; it is not imported, so `#[use_type]` neither lists nor rewrites it. - -## Syntax Grammar - -The attribute argument of `#[cgp_component]` is either a bare provider name or a comma-separated set of keyed values: - -```ebnf -CgpComponentArgs -> ProviderName - | KeyValueArg ( `,` KeyValueArg )* `,`? - -ProviderName -> IDENTIFIER - -KeyValueArg -> `name` `:` ComponentName - | `provider` `:` IDENTIFIER - | `context` `:` IDENTIFIER - -ComponentName -> IDENTIFIER GenericArgs? -``` - -`ProviderName` is the bare-identifier form and is shorthand for setting `provider` alone. In the key/value form each of the three keys may appear at most once and in any order, and `provider` is required — the other two have defaults (`context` is `__Context__`, `name` is the provider name with a `Component` suffix). `IDENTIFIER` is a Rust identifier token, and `GenericArgs` is the Rust grammar's `< … >` argument list (so the component name may carry generic parameters while the provider name may not). The attribute delimiter shown in Syntax — `(...)` for the bare form and `{...}` for the key/value form — is ordinary Rust attribute syntax; the argument tokens inside follow this grammar regardless of which delimiter is used. - -## Expansion - -`#[cgp_component]` replaces the annotated trait with five top-level items plus a set of standard provider impls. Starting from this input: - -```rust -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} -``` - -the macro emits, first, the **consumer trait** unchanged from its definition: - -```rust -pub trait CanCalculateArea { - fn area(&self) -> f64; -} -``` - -Second, it emits the **provider trait**, which is the consumer trait with `Self` replaced by an explicit leading `Context` type parameter and every `self`/`Self` reference rewritten to `context`/`Context`. The provider trait carries an [`IsProviderFor`](../traits/is_provider_for.md) supertrait that captures the component name and context so that unsatisfied dependencies surface as readable compiler errors. The supertrait's third argument is the `Params` tuple of the component's extra type parameters; for a component with no parameters beyond the context it is the empty `()`: - -```rust -pub trait AreaCalculator: - IsProviderFor -{ - fn area(context: &Context) -> f64; -} -``` - -Third, it emits the **consumer blanket impl**, which says that any context implementing the provider trait *for itself* automatically gets the consumer trait. This is the bridge that lets callers write `context.area()`: - -```rust -impl CanCalculateArea for Context -where - Context: AreaCalculator, -{ - fn area(&self) -> f64 { - Context::area(self) - } -} -``` - -Fourth, it emits the **provider blanket impl**, which lets any provider that delegates this component (via [`DelegateComponent`](../traits/delegate_component.md)) inherit the provider trait from the provider it delegates to. This is the mechanism `delegate_components!` drives — it turns a context into a type-level table whose entry for `AreaCalculatorComponent` names the chosen provider: - -```rust -impl AreaCalculator for Provider -where - Provider: DelegateComponent - + IsProviderFor, - Provider::Delegate: AreaCalculator, -{ - fn area(context: &Context) -> f64 { - Provider::Delegate::area(context) - } -} -``` - -Note that the `IsProviderFor` bound sits on `Provider` itself, beside the `DelegateComponent` bound — not on `Provider::Delegate`. The blanket impl of `IsProviderFor` (generated by `delegate_components!`) forwards a component's dependencies through `Provider`, so requiring `Provider: IsProviderFor<…>` is what threads those dependencies down the delegation chain and surfaces them in error messages. - -Fifth, it emits the **component name struct**, a zero-sized marker that serves as the key into delegation tables: - -```rust -pub struct AreaCalculatorComponent; -``` - -Beyond these five items, the macro also generates standard provider impls that make the component usable in the patterns CGP relies on. It emits a [`UseContext`](../providers/use_context.md) impl, so that the provider trait can be satisfied by routing back through a context's own consumer-trait implementation; a `RedirectLookup` impl, used by the namespace and preset machinery; and, for each [`#[derive_delegate(...)]`](../attributes/derive_delegate.md) attribute present, a [`UseDelegate`](../providers/use_delegate.md) impl that dispatches on the named generic parameter. When the component is defined inside a namespace, prefix impls are emitted as well (see [`#[cgp_namespace]`](cgp_namespace.md)). - -Two details of the expansion are worth holding onto because they are easy to get wrong. The generated type parameters carry reserved names, not the readable ones used above: the context parameter is literally `__Context__` unless overridden, and the provider parameter in the provider blanket impl is `__Provider__`. The examples here use `Context` and `Provider` for legibility, but the emitted code uses the reserved names. And the generic parameters of a component with type parameters (for example `CanCalculateArea`) are appended *after* the context parameter in the provider trait and grouped into a parenthesized list in the `IsProviderFor` `Params` position — so `IsProviderFor` for one parameter and `(Shape, Scalar)` for several — see generic parameters in the `/cgp` skill for the multi-parameter rules. - -## Examples - -A complete component, from definition through wiring to use, ties the pieces together. First the component and a provider for it: - -```rust -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_auto_getter] -pub trait HasDimensions { - fn width(&self) -> &f64; - fn height(&self) -> &f64; -} - -#[cgp_impl(new RectangleArea)] -impl AreaCalculator -where - Self: HasDimensions, -{ - fn area(&self) -> f64 { - self.width() * self.height() - } -} -``` - -Then a concrete context that wires the component to that provider and uses it: - -```rust -#[derive(HasField)] -pub struct Rectangle { - pub width: f64, - pub height: f64, -} - -delegate_components! { - Rectangle { - AreaCalculatorComponent: RectangleArea, - } -} - -fn print_area(rect: &Rectangle) { - println!("area = {}", rect.area()); // CanCalculateArea, via RectangleArea -} -``` - -The call `rect.area()` resolves through the consumer blanket impl to `Rectangle::area(rect)`, which resolves through the provider blanket impl to `RectangleArea::area(rect)` because `Rectangle`'s table maps `AreaCalculatorComponent` to `RectangleArea`. - -## Related constructs - -`#[cgp_component]` is the root that most other constructs attach to. [`#[cgp_impl]`](cgp_impl.md) and [`#[cgp_provider]`](cgp_provider.md) are the idiomatic ways to write providers for a component; [`#[cgp_fn]`](cgp_fn.md) is the lighter-weight alternative when only one implementation is ever needed. [`delegate_components!`](delegate_components.md) wires a component to a provider on a concrete context, and [`check_components!`](check_components.md) verifies at compile time that the wiring is complete. The specialized forms [`#[cgp_type]`](cgp_type.md) and [`#[cgp_getter]`](cgp_getter.md) extend `#[cgp_component]` for abstract types and getters. The attributes [`#[derive_delegate]`](../attributes/derive_delegate.md), [`#[extend]`](../attributes/extend.md), and [`#[use_type]`](../attributes/use_type.md) modify what the macro generates. - -## Known issues - -A const generic parameter on the trait is not supported and is rejected with a compile error. Because the provider trait records a component's extra parameters as a tuple of *types* in its `IsProviderFor` supertrait, and CGP's wiring dispatches on types rather than values, a const value has nowhere to live in that machinery. A const *item* on the trait is unaffected — writing `const CONSTANT: u64;` as a trait member is an associated const, not a generic parameter, and is supplied by a const-generic provider struct (for example `UseConstant`) in the usual way. - -## Source - -- Entry point: `cgp_component` in [crates/macros/cgp-macro-lib/src/cgp_component.rs](../../../crates/macros/cgp-macro-lib/src/cgp_component.rs), which drives the `preprocess → eval → to_items` pipeline. -- Logic: [crates/macros/cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/) — argument parsing in `args/`, the provider trait and blanket impls in `preprocessed/`, the standard provider impls (`UseContext`, `RedirectLookup`, `UseDelegate`) in `evaluated/`. -- Default identifiers `__Context__` and `{Provider}Component`: set in `args/component_args.rs`. -- Internal walkthrough (pipeline stages, synthesizing functions, corner cases, and the index of tests and snapshots): [implementation/entrypoints/cgp_component.md](../../implementation/entrypoints/cgp_component.md). diff --git a/docs/reference/macros/cgp_computer.md b/docs/reference/macros/cgp_computer.md deleted file mode 100644 index 53e7cf61..00000000 --- a/docs/reference/macros/cgp_computer.md +++ /dev/null @@ -1,151 +0,0 @@ -# `#[cgp_computer]` - -`#[cgp_computer]` is an attribute macro that turns a plain function into a [`Computer`](../components/computer.md) provider, generating the provider struct, the provider impl, and the wiring that fills in the rest of the handler family by promotion. - -## Purpose - -`#[cgp_computer]` exists to make the simplest handler — a pure function from input to output — definable as an ordinary Rust function, the way [`#[cgp_fn]`](cgp_fn.md) makes a blanket-impl trait definable as a function. A handler in CGP is normally a provider struct with one or more impls across the [`Computer`](../components/computer.md), [`TryComputer`](../components/try_computer.md), `AsyncComputer`, and [`Handler`](../components/handler.md) traits, each carrying the `context`/`code`/`input` plumbing. Writing that by hand for a computation as small as "add two numbers" is disproportionate boilerplate. `#[cgp_computer]` lets the author write just the computation as a function and have the macro synthesize the provider and wire it into every member of the handler family. - -The macro encodes a single decision: whether the function is synchronous or asynchronous, and whether it returns a plain value or a `Result`. From that it picks the right base trait to implement and the right [promotion bundle](../providers/handler_combinators.md) to derive the rest of the family from. The result is that one function definition yields a provider usable wherever any handler shape — `compute`, `try_compute`, `compute_async`, `handle`, and their `…Ref` variants — is expected. - -## Syntax - -`#[cgp_computer]` is applied to a free function and accepts an optional provider name as its argument: - -```rust -#[cgp_computer] -fn add(a: u64, b: u64) -> u64 { - a + b -} - -#[cgp_computer(MyAdder)] -fn add(a: u64, b: u64) -> u64 { - a + b -} -``` - -When the argument is omitted, the generated provider struct takes the function name converted to PascalCase — `add` becomes `Add`. When an argument is given, it is used verbatim as the provider name. The function's parameters become the handler's input, its return type becomes the handler's output, and its generic parameters and `where` clause carry over to the generated impl. The function may not have a `self` receiver, since a handler provider has no receiver — the context is supplied separately by the handler machinery. The function may be `async`, which selects the asynchronous base trait described below. - -## Syntax Grammar - -The attribute argument of `#[cgp_computer]` is a single optional provider name: - -```ebnf -CgpComputerArgs -> ProviderName? - -ProviderName -> IDENTIFIER -``` - -When the argument is omitted, the generated provider struct takes the function name converted to PascalCase; a given `IDENTIFIER` is used verbatim. The shape of the annotated function — its parameters, return type, `async`-ness, generics, and `where` clause — is plain Rust and is read by the macro to choose the base trait and promotion bundle, as described in Expansion. - -## Expansion - -The macro emits three items: the original function unchanged, a `#[cgp_new_provider]` impl of a base handler trait that calls the function, and a `delegate_components!` block that wires the remaining handler components to a promotion bundle. The base trait and the bundle are chosen from two independent axes — sync versus async, and value-returning versus `Result`-returning. - -For a synchronous function returning a plain value, the base trait is [`Computer`](../components/computer.md). The function's parameters are collected into a tuple that becomes the single `Input` type, and the body destructures that tuple back into the original arguments before calling the function. Given - -```rust -#[cgp_computer] -fn add(a: u64, b: u64) -> u64 { - a + b -} -``` - -the macro expands to the function plus: - -```rust -#[cgp_new_provider] -impl<__Context__, __Code__> Computer<__Context__, __Code__, (u64, u64)> for Add { - type Output = u64; - - fn compute( - _context: &__Context__, - _code: PhantomData<__Code__>, - (arg_0, arg_1): (u64, u64), - ) -> Self::Output { - add(arg_0, arg_1) - } -} - -delegate_components! { - Add { - [ - ComputerRefComponent, - TryComputerComponent, - TryComputerRefComponent, - AsyncComputerComponent, - AsyncComputerRefComponent, - HandlerComponent, - HandlerRefComponent, - ] -> - PromoteComputer, - } -} -``` - -The `#[cgp_new_provider]` attribute defines the `Add` struct and the `IsProviderFor` impl alongside the `Computer` impl, exactly as it does for any provider. The context and code generic parameters are introduced under the reserved names `__Context__` and `__Code__`. The `delegate_components!` block routes every other handler component to [`PromoteComputer`](../providers/handler_combinators.md), the promotion bundle that derives `TryComputer`, `AsyncComputer`, `Handler`, and all the `…Ref` variants from a `Computer` base. The result is that `Add` answers `compute`, `try_compute`, `compute_async`, and `handle`, all computing `a + b`. - -When the function returns a `Result`, the base trait stays `Computer` — its `Output` is the `Result` type as written — but the promotion bundle changes to [`PromoteTryComputer`](../providers/handler_combinators.md), which interprets the `Result`-valued computer as a genuinely fallible handler. So - -```rust -#[cgp_computer] -fn add_with_error(a: u64, b: u64) -> Result { - a.checked_add(b).ok_or_else(|| "Overflow".to_string()) -} -``` - -produces a `Computer` impl whose `Output` is `Result`, and a `delegate_components!` block routing the rest of the family to `PromoteTryComputer`. Through that bundle, `try_compute` and `handle` surface the `Ok`/`Err` outcome as the handler's success or failure rather than as a plain value. - -When the function is `async`, the base trait is `AsyncComputer` instead of `Computer`, the generated method is `compute_async` and `.await`s the function call, and the promotion bundle is the async-base counterpart. A plain-value async function wires the remaining components to [`PromoteAsyncComputer`](../providers/handler_combinators.md); an async function returning a `Result` wires them to [`PromoteHandler`](../providers/handler_combinators.md). In both async cases the macro delegates a smaller set of components — `AsyncComputerRefComponent`, `HandlerComponent`, and `HandlerRefComponent` — since the synchronous members of the family are not derived from an async base. - -Generic parameters and bounds on the function flow into the impl. A function such as - -```rust -#[cgp_computer] -pub fn add_generic>(a: T, b: T) -> T { - a + b -} -``` - -carries its `T` and the `T: Add` bound onto the generated `Computer` impl (appended ahead of the introduced `__Context__` and `__Code__` parameters), so the provider is itself generic over `T`. A reference parameter is likewise preserved: `fn to_string_ref(value: &Value) -> String` becomes a `Computer` whose input tuple is `(&Value)`, and the `PromoteComputer` bundle's `PromoteRef` entries make it serve the `…Ref` components as well. - -## Examples - -A self-contained use defines two computers and wires a context that supplies the error type, then exercises every handler shape from the single function definitions: - -```rust -use cgp::prelude::*; -use cgp::extra::handler::{Computer, TryComputer, AsyncComputer, Handler}; - -#[cgp_computer] -fn add(a: u64, b: u64) -> u64 { - a + b -} - -pub struct App; - -delegate_components! { - App { - ErrorTypeProviderComponent: UseType, - } -} - -// All four shapes are answered by the single `add` definition: -// Add::compute(&App, PhantomData::<()>, (1, 2)) == 3 -// Add::try_compute(&App, PhantomData::<()>, (1, 2)) == Ok(3) -// Add::compute_async(&App, PhantomData::<()>, (1, 2)) resolves to 3 -// Add::handle(&App, PhantomData::<()>, (1, 2)) resolves to Ok(3) -``` - -Because the function returns a plain `u64`, the `try_compute` and `handle` forms always succeed with `Ok`. Switching the function to return `Result` instead — as `add_with_error` above — makes those same forms propagate the `Err` when the computation fails, with no change to the call sites beyond the now-fallible outcome. - -## Related constructs - -`#[cgp_computer]` defines a provider for the [`Computer`](../components/computer.md) component (or `AsyncComputer` for async functions), part of the handler family described in [handlers](../../concepts/handlers.md). It is the handler-world analogue of [`#[cgp_fn]`](cgp_fn.md), which defines a blanket-impl trait from a function; and the input-less counterpart [`#[cgp_producer]`](cgp_producer.md) defines a [`Producer`](../components/producer.md) the same way. The generated impl is emitted through [`#[cgp_new_provider]`](cgp_new_provider.md), and the rest of the handler family is filled in by the [promotion bundles](../providers/handler_combinators.md) (`PromoteComputer`, `PromoteTryComputer`, `PromoteAsyncComputer`, `PromoteHandler`) wired through [`delegate_components!`](delegate_components.md). - -## Source - -- Entrypoint: [crates/macros/cgp-extra-macro/src/lib.rs](../../../crates/macros/cgp-extra-macro/src/lib.rs), forwarding to the implementation in [crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_computer.rs). -- `Result`-versus-value detection: the `MaybeResultType` parser in [crates/macros/cgp-extra-macro-lib/src/parse/maybe_result.rs](../../../crates/macros/cgp-extra-macro-lib/src/parse/maybe_result.rs). -- Base `Computer`/`AsyncComputer` traits: [crates/extra/cgp-handler/src/components/](../../../crates/extra/cgp-handler/src/components/); the promotion bundles in [crates/extra/cgp-handler/src/providers/promote_all.rs](../../../crates/extra/cgp-handler/src/providers/promote_all.rs). -- Internal walkthrough (the sync/async and value/`Result` branching, the generated items, and the index of behavioral tests): [implementation/entrypoints/cgp_computer.md](../../implementation/entrypoints/cgp_computer.md). diff --git a/docs/reference/macros/cgp_fn.md b/docs/reference/macros/cgp_fn.md deleted file mode 100644 index 5f25a863..00000000 --- a/docs/reference/macros/cgp_fn.md +++ /dev/null @@ -1,176 +0,0 @@ -# `#[cgp_fn]` - -`#[cgp_fn]` turns a plain Rust function into a single-implementation CGP capability — it generates a trait and a blanket impl for every context from one function body, so a context gains the method with no separate wiring step. - -## Purpose - -`#[cgp_fn]` exists to make the simplest, most common form of CGP reachable with nothing more than a function. Writing a capability by hand means defining a trait, writing a blanket impl over a generic context, and threading the dependencies the body needs through that impl's `where` clause. `#[cgp_fn]` collapses all of that into a single function: you write the body as if `self` were a concrete value, mark the values you want pulled from the context with `#[implicit]`, and the macro produces the trait and the blanket impl that wires it up. - -The result is a capability that any context implements automatically, as long as the context can satisfy the impl-side dependencies. Because the generated impl is a blanket impl over a generic context, there is no `delegate_components!` call, no provider type, and no component name — the method simply becomes available on every type that has the fields the body reads. This is what makes `#[cgp_fn]` the recommended entry point for basic CGP: a reader only needs to understand plain Rust functions to use it, and the trait machinery stays hidden. - -The trade-off against [`#[cgp_component]`](cgp_component.md) is single versus multiple implementations. A `#[cgp_component]` trait can have many alternative providers, one chosen per context through wiring; that flexibility is exactly what costs the extra ceremony. `#[cgp_fn]` permits only one implementation — the function body — and in exchange removes the wiring entirely. Reach for `#[cgp_fn]` when a capability has a single natural definition, and graduate to `#[cgp_component]` only when a context genuinely needs to swap in a different implementation. The two interoperate: a `#[cgp_fn]` capability can depend on a `#[cgp_component]` one and vice versa through [`#[uses]`](../attributes/uses.md). - -## Syntax - -`#[cgp_fn]` is applied as an attribute on a free function whose first parameter is `&self` (or `&mut self`). The function name, in snake case, becomes the generated method name; the trait name defaults to that function name converted to PascalCase. - -```rust -#[cgp_fn] -pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} -``` - -Any parameter marked `#[implicit]` is removed from the method signature and instead fetched from a field of the context whose name matches the parameter. The function above therefore generates a `RectangleArea` trait with a `rectangle_area(&self) -> f64` method, and the body reads `width` and `height` from the context rather than from arguments. - -The trait name can be set explicitly by passing an identifier as the attribute argument, which overrides the PascalCase default. This is useful when the verb-style trait name reads better than the function name: - -```rust -#[cgp_fn(CanCalculateRectangleArea)] -pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} -``` - -Generic parameters and a `where` clause on the function are handled with a deliberate split. Every generic parameter declared in the function's `<...>` list goes onto both the generated trait and the impl. The `where` clause, by contrast, is treated as an impl-side dependency: it lands only on the impl, hidden from the trait interface, exactly as the constraints in a hand-written blanket impl would be. - -```rust -#[cgp_fn] -pub fn rectangle_area( - &self, - #[implicit] width: Scalar, - #[implicit] height: Scalar, -) -> Scalar -where - Scalar: Mul + Copy, -{ - width * height -} -``` - -Here `Scalar` appears on both `RectangleArea` (the trait) and its impl, while `Scalar: Mul + Copy` appears only on the impl. A bound that should apply to the impl but not be a trait parameter can instead be written with the `#[impl_generics(...)]` attribute, which adds generic parameters to the impl block alone: - -```rust -#[cgp_fn] -#[impl_generics(Name: Display)] -pub fn greet(&self, #[implicit] name: &Name) -> String { - format!("Hello, {}!", name) -} -``` - -One restriction is intentional: `#[cgp_fn]` does not support generics on the desugared *method* itself. Generics belong to the trait and impl, not to the generated method signature. Method-level generics are uncommon in CGP and, where genuinely needed, are considered an advanced case better written as an explicit blanket impl or a [`#[cgp_component]`](cgp_component.md) provider. - -Several companion attributes refine the generated code and are documented separately. [`#[uses(...)]`](../attributes/uses.md) adds trait bounds on `Self` as impl-side dependencies; [`#[use_type(...)]`](../attributes/use_type.md) imports an abstract type and rewrites its occurrences to fully-qualified form; [`#[use_provider(...)]`](../attributes/use_provider.md) supports higher-order providers; [`#[extend(...)]`](../attributes/extend.md) adds supertrait bounds to the generated trait; and [`#[extend_where(...)]`](../attributes/extend_where.md) adds `where` predicates to the generated trait definition. - -## Syntax Grammar - -The attribute argument of `#[cgp_fn]` is a single optional trait name: - -```ebnf -CgpFnArgs -> TraitName? - -TraitName -> IDENTIFIER -``` - -When the argument is omitted, the trait name defaults to the function name converted to PascalCase. The `#[implicit]` markers on parameters and the companion attributes (`#[uses]`, `#[use_type]`, `#[extend]`, and the rest) are separate attributes with their own grammars, documented on their own pages. - -## Expansion - -`#[cgp_fn]` emits exactly two items: the trait carrying the method, and a blanket impl of that trait for a generic context. Starting from the basic form: - -```rust -#[cgp_fn] -pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} -``` - -the macro produces the trait — the `#[implicit]` parameters stripped from the signature — followed by the blanket impl over the reserved context type `__Context__`. The implicit parameters become `HasField` bounds on the impl and `get_field` bindings at the top of the body: - -```rust -pub trait RectangleArea { - fn rectangle_area(&self) -> f64; -} - -impl<__Context__> RectangleArea for __Context__ -where - Self: HasField - + HasField, -{ - fn rectangle_area(&self) -> f64 { - let width: f64 = self.get_field(PhantomData::).clone(); - let height: f64 = self.get_field(PhantomData::).clone(); - - width * height - } -} -``` - -The generated context type parameter is literally `__Context__`, not `Context` — the same reserved name `#[cgp_component]` uses — and references to it inside the impl appear as `Self`. The `Symbol!("...")` shorthand stands for the type-level string the macro actually emits (for `width`, `Symbol<5, Chars<'w', Chars<'i', Chars<'d', Chars<'t', Chars<'h', Nil>>>>>>`). Each implicit binding follows the same conversion rules as [`#[cgp_auto_getter]`](cgp_auto_getter.md): an owned value gets a trailing `.clone()`, and a `&str` return gets `.as_str()`, while a borrowed `&Name` is taken by reference with no conversion. - -Generics and the `where` clause expand according to the split described above. Given the `Scalar` example, the generic goes on both trait and impl while the function's `where` bound stays on the impl, ordered before the implicit `HasField` bounds — the implicit bounds are always appended last: - -```rust -pub trait RectangleArea { - fn rectangle_area(&self) -> Scalar; -} - -impl<__Context__, Scalar> RectangleArea for __Context__ -where - Scalar: Mul + Copy, - Self: HasField - + HasField, -{ - fn rectangle_area(&self) -> Scalar { /* ... */ } -} -``` - -The bounds contributed by the companion attributes are layered into this same impl. `#[uses(Trait)]` and `#[extend(Trait)]` push a `Self: Trait` predicate onto the impl's `where` clause; `#[extend(Trait)]` additionally adds `Trait` as a supertrait of the generated trait, and `#[extend_where(...)]` adds its predicates to the trait's own `where` clause. `#[impl_generics(...)]` inserts its parameters into the impl generics only. The implicit-argument bounds are always appended last, after the attribute-contributed predicates. - -## Examples - -A two-layer capability shows `#[cgp_fn]` composing with itself through `#[uses]`. The first function defines the base area calculation; the second builds a scaled version on top of it, declaring its dependency on the first with `#[uses(RectangleArea)]`: - -```rust -use cgp::prelude::*; - -#[cgp_fn] -pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} - -#[cgp_fn] -#[uses(RectangleArea)] -pub fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { - self.rectangle_area() * scale_factor * scale_factor -} -``` - -A concrete context only needs the right fields; no wiring is required. Deriving `HasField` is enough for both capabilities to apply automatically: - -```rust -#[derive(HasField)] -pub struct Rectangle { - pub width: f64, - pub height: f64, - pub scale_factor: f64, -} - -fn report(rect: &Rectangle) { - println!("base area = {}", rect.rectangle_area()); - println!("scaled area = {}", rect.scaled_rectangle_area()); -} -``` - -Because `Rectangle` derives `HasField` and carries `width`, `height`, and `scale_factor`, it satisfies the `HasField` bounds on both generated impls, so `rect.rectangle_area()` and `rect.scaled_rectangle_area()` both resolve directly through the blanket impls — there is no `delegate_components!` block anywhere in this example. - -## Related constructs - -`#[cgp_fn]` is the lightweight counterpart to [`#[cgp_component]`](cgp_component.md): both produce a capability usable through a method call, but `#[cgp_fn]` allows a single implementation with no wiring, whereas `#[cgp_component]` allows many providers selected per context through [`delegate_components!`](delegate_components.md). It shares its `#[implicit]` argument mechanism with [`#[cgp_impl]`](cgp_impl.md), and the field-access semantics with [`#[cgp_auto_getter]`](cgp_auto_getter.md) and the underlying [`HasField`](../traits/has_field.md) trait. The companion attributes [`#[uses]`](../attributes/uses.md), [`#[use_type]`](../attributes/use_type.md), [`#[use_provider]`](../attributes/use_provider.md), [`#[extend]`](../attributes/extend.md), and [`#[extend_where]`](../attributes/extend_where.md) shape what the macro generates. Like a hand-written extension trait, the impl `#[cgp_fn]` emits is a blanket impl in the style of [`#[blanket_trait]`](blanket_trait.md); the difference is that `#[cgp_fn]` derives the trait and its body from a function rather than from a trait with default methods. - -## Source - -- Entry point: `cgp_fn` in [crates/macros/cgp-macro-lib/src/cgp_fn.rs](../../../crates/macros/cgp-macro-lib/src/cgp_fn.rs), which parses the optional trait-name identifier and the function, then runs `item.preprocess()?.to_items()?`. -- Logic: [crates/macros/cgp-macro-core/src/types/cgp_fn/](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/) — `item.rs` performs the PascalCase default-name derivation (`to_camel_case_str`), implicit-argument extraction, and attribute parsing; `preprocessed.rs` builds the trait in `to_item_trait` and the blanket impl in `to_item_impl`, including the generics/`where`-clause split and the insertion of the leading `__Context__` parameter. -- Implicit-argument handling: [crates/macros/cgp-macro-core/src/types/implicits/](../../../crates/macros/cgp-macro-core/src/types/implicits/); the companion-attribute parsing in [crates/macros/cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs). -- Internal walkthrough (the pipeline stages, the function that synthesizes each generated item, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/cgp_fn.md](../../implementation/entrypoints/cgp_fn.md). diff --git a/docs/reference/macros/cgp_getter.md b/docs/reference/macros/cgp_getter.md deleted file mode 100644 index f707f044..00000000 --- a/docs/reference/macros/cgp_getter.md +++ /dev/null @@ -1,156 +0,0 @@ -# `#[cgp_getter]` - -`#[cgp_getter]` defines a getter as a full CGP component — the same convenience as [`#[cgp_auto_getter]`](cgp_auto_getter.md), but wired through [`#[cgp_component]`](cgp_component.md) and backed by a [`UseField`](../providers/use_field.md) provider so the field name can differ from the method name and the getter can be swapped per context. - -## Purpose - -`#[cgp_getter]` exists for the advanced case where a getter must participate in CGP wiring rather than resolve to a single blanket impl. It is a specialized tool, not a default: most getters read a same-named field, which an [`#[implicit]`](../attributes/implicit.md) argument (for a private read) or [`#[cgp_auto_getter]`](cgp_auto_getter.md) (for a published accessor) handles with no wiring. Reserve `#[cgp_getter]` for when a context needs full control over the getter's implementation — storing the value under a different field name, or supplying it in different ways per context. It delivers that control by making the getter a genuine component with a provider trait, a component name, and a delegation entry. - -The trade-off against `#[cgp_auto_getter]` is wireable versus blanket. `#[cgp_auto_getter]` emits one blanket impl that fires automatically for any context whose field name matches the method name; there is nothing to wire and nothing to choose. `#[cgp_getter]` emits a component that a context must wire to a provider, but in exchange the field name is decoupled from the method name and the implementation can be selected per context. You pay one line of wiring to gain that decoupling. - -The decoupling is delivered through the `UseField` pattern. `#[cgp_getter]` automatically generates a `UseField` provider impl for the getter, so a context wires the getter to `UseField` and names whichever field it actually stores the value in — the field name lives in the wiring, not in the trait. Like any getter trait, a `#[cgp_getter]` trait can also be implemented directly on a concrete context when no wiring is desired. - -## Syntax - -The macro is applied to a getter trait the same way `#[cgp_auto_getter]` is, and accepts the same getter-method forms — `&self`/`&mut self` receivers and the `&str`, `Option<&T>`, `Option<&str>`, `&[T]`, owned, and associated-type return shorthands. The simplest form takes no argument: - -```rust -#[cgp_getter] -pub trait HasName { - fn name(&self) -> &str; -} -``` - -Because `#[cgp_getter]` is an extension of `#[cgp_component]`, it needs a provider trait name, and it derives one from the trait name by default. When the trait name begins with `Has`, the macro strips that prefix and appends `Getter`, so `HasName` yields the provider `NameGetter` (and component name `NameGetterComponent`). You can override the provider name by passing it as an argument, exactly as with `#[cgp_component]`: - -```rust -#[cgp_getter(GetName)] -pub trait HasName { - fn name(&self) -> &str; -} -``` - -Here the provider trait is named `GetName` and the component `GetNameComponent`. The defaulting rule means `#[cgp_getter]` is at its most ergonomic when getter traits follow the `Has{Field}` naming convention. - -## Syntax Grammar - -The attribute argument of `#[cgp_getter]` is the same grammar as [`#[cgp_component]`](cgp_component.md)'s `CgpComponentArgs` — a bare provider name or the keyed `name`/`provider`/`context` form: - -```ebnf -CgpGetterArgs -> CgpComponentArgs // see #[cgp_component] -``` - -The only difference from `#[cgp_component]` is the default applied when `provider` is omitted: the macro derives the provider name from the trait name by stripping a leading `Has` and appending `Getter` (so `HasName` yields `NameGetter`). All other keys and their defaults behave exactly as documented for `#[cgp_component]`. - -## Expansion - -`#[cgp_getter]` expands to everything `#[cgp_component]` emits, plus a set of getter-specific provider impls. The component part is identical to a `#[cgp_component(NameGetter)]` definition — the consumer trait, the provider trait, the consumer and provider blanket impls, the `NameGetterComponent` marker, and the standard `UseContext` and `RedirectLookup` provider impls (see [`#[cgp_component]`](cgp_component.md) for that core expansion). On top of those, the macro adds the getter providers described below. A `UseFields` provider is always emitted; the `UseField` and `WithProvider` providers are emitted only when the getter trait has exactly one method, since both presuppose a single field to read. - -The most important addition is the `UseField` provider impl, which is what lets the field name differ from the method name. Starting from the single-method trait: - -```rust -#[cgp_getter] -pub trait HasName { - fn name(&self) -> &str; -} -``` - -the macro generates this impl for `UseField<__Tag__>`, where `__Tag__` is a free generic parameter standing in for the field name the context will choose at wiring time: - -```rust -impl<__Context__, __Tag__> NameGetter<__Context__> for UseField<__Tag__> -where - __Context__: HasField<__Tag__, Value = String>, -{ - fn name(__context__: &__Context__) -> &str { - __context__.get_field(PhantomData::<__Tag__>).as_str() - } -} -``` - -This is the contrast with `#[cgp_auto_getter]`, whose blanket impl hard-codes the tag to `Symbol!("name")`. Here the tag is a parameter, so wiring to `UseField` supplies `first_name` as `__Tag__` and the getter reads that field instead. The `&str` shorthand is handled the same way in both macros: the `Value` is `String` and the body appends `.as_str()`. - -When the getter trait contains exactly one method, the macro additionally generates a `WithProvider` impl, which adapts a [field-getter](../providers/use_field.md) provider into the getter component: - -```rust -impl<__Context__, __Provider__> NameGetter<__Context__> for WithProvider<__Provider__> -where - __Provider__: FieldGetter<__Context__, NameGetterComponent, Value = String>, -{ - fn name(__context__: &__Context__) -> &str { - __Provider__::get_field(__context__, PhantomData::).as_str() - } -} -``` - -Finally, the macro generates a `UseFields` impl — the analogue of the `#[cgp_auto_getter]` blanket impl, but as a provider — which reads each method's field keyed by the method name as a `Symbol!`: - -```rust -impl<__Context__> NameGetter<__Context__> for UseFields -where - __Context__: HasField, -{ - fn name(__context__: &__Context__) -> &str { - __context__.get_field(PhantomData::).as_str() - } -} -``` - -Each of these provider impls is paired with a matching `IsProviderFor` impl carrying the same `where` bounds, so that delegation propagates the dependency and check traits can report missing fields precisely. As elsewhere, the desugarings show `Symbol!("name")` in sugared form rather than its expanded `Symbol<...>` representation. - -## Examples - -A typical use wires the getter to `UseField` with a field name that differs from the method name, which is the case `#[cgp_auto_getter]` cannot express. The context stores the value in `first_name`, while the trait method is `name`: - -```rust -use cgp::prelude::*; - -#[cgp_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[derive(HasField)] -pub struct Person { - pub first_name: String, -} - -delegate_components! { - Person { - NameGetterComponent: UseField, - } -} - -fn greet(person: &Person) { - println!("Hello, {}!", person.name()); // reads the first_name field -} -``` - -Because `Person` wires `NameGetterComponent` to `UseField`, the generated `UseField` provider reads `Person`'s `first_name` field to implement `name()`, so `person.name()` returns the value stored in `first_name`. - -As with any getter trait, you can skip the wiring entirely and implement the consumer trait directly on a concrete context: - -```rust -pub struct Person { - pub full_name: String, -} - -impl HasName for Person { - fn name(&self) -> &str { - &self.full_name - } -} -``` - -The direct implementation is the most transparent option and shows that a `#[cgp_getter]` trait is, at bottom, an ordinary CGP component whose consumer trait can be implemented like any Rust trait. - -## Related constructs - -`#[cgp_getter]` is the wireable counterpart to [`#[cgp_auto_getter]`](cgp_auto_getter.md): the latter emits a single `HasField` blanket impl keyed by the method name, while `#[cgp_getter]` emits a full component plus a `UseField` provider so the field name can be chosen at wiring time. It is built on [`#[cgp_component]`](cgp_component.md), inheriting that macro's entire expansion and provider-name defaulting, and it is wired with [`delegate_components!`](delegate_components.md) and verified with [`check_components!`](check_components.md). The generated provider keys off [`UseField`](../providers/use_field.md) and reads fields produced by [`#[derive(HasField)]`](../derives/derive_has_field.md), keyed by [`Symbol!`](symbol.md). When the getter's return type is an abstract associated type, the construct overlaps with [`#[cgp_type]`](cgp_type.md). - -## Source - -- Entry point: `cgp_getter` in [crates/macros/cgp-macro-lib/src/cgp_getter.rs](../../../crates/macros/cgp-macro-lib/src/cgp_getter.rs), which derives the default provider name (strip `Has`, append `Getter`), runs the `#[cgp_component]` `preprocess → eval` pipeline, then converts the result into `ItemCgpGetter` and emits the extra provider impls. -- Logic: [crates/macros/cgp-macro-core/src/types/cgp_getter/](../../../crates/macros/cgp-macro-core/src/types/cgp_getter/) — `item.rs` assembles the items, `use_field.rs` builds the `UseField` impl with the free `__Tag__` parameter, `to_use_fields_impl.rs` builds the `UseFields` impl keyed by method name, and `with_provider.rs` builds the `WithProvider` impl. -- Getter-method parsing and the return-type shorthands, shared with `#[cgp_auto_getter]`: [crates/macros/cgp-macro-core/src/functions/getter/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/getter/parse.rs) and [crates/macros/cgp-macro-core/src/types/getter/](../../../crates/macros/cgp-macro-core/src/types/getter/). -- Internal walkthrough (the pipeline stages, the function that synthesizes each generated item, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/cgp_getter.md](../../implementation/entrypoints/cgp_getter.md). diff --git a/docs/reference/macros/cgp_impl.md b/docs/reference/macros/cgp_impl.md deleted file mode 100644 index 25231014..00000000 --- a/docs/reference/macros/cgp_impl.md +++ /dev/null @@ -1,185 +0,0 @@ -# `#[cgp_impl]` - -`#[cgp_impl]` is the idiomatic way to write a provider for a CGP component: it lets you write a provider implementation using consumer-trait-style syntax — `impl Trait for Context` with `self` and `Self` — and desugars it into the underlying provider-trait implementation that CGP actually requires. - -## Purpose - -`#[cgp_impl]` exists to hide the awkward shape of a raw provider impl behind syntax that reads like an ordinary trait implementation. A CGP provider trait, as generated by [`#[cgp_component]`](cgp_component.md), moves the original `Self` into an explicit leading `Context` type parameter and is implemented *for the named provider struct*, not for the context. Written by hand, that impl looks inside-out: the trait is `AreaCalculator`, the `Self` type is a dummy struct like `RectangleArea`, the receiver becomes a plain `context: &Context` parameter, and the body can never mention `self`. This is correct but unfamiliar, and it obscures the fact that a provider is conceptually just "an implementation of the consumer trait." - -`#[cgp_impl]` restores the familiar shape. You write the impl as though you were implementing the consumer trait directly on the context — keeping `self`, `Self`, and the consumer trait's method signatures — and the macro mechanically rewrites it into the provider-trait form. The provider's name is given in the attribute argument rather than in the `Self` position, so the impl header reads `impl AreaCalculator for Context` with `RectangleArea` named in the attribute. - -The one fact this convenience must not let you forget is that there is no provider value at runtime. Inside a `#[cgp_impl]` block, `self` and `Self` refer to the **context**, not to the provider struct. The provider struct is a type-level-only marker that names the implementation; it is never instantiated, and it has no fields you can read. The macro converts every `self` to the context value and every `Self` to the context type precisely because the context is the only value that exists when the method runs. - -## Syntax - -`#[cgp_impl]` is applied to an `impl` block, and its attribute argument names the provider. The provider name carries an optional `new` keyword and an optional explicit component type: - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator -where - Self: HasDimensions, -{ - fn area(&self) -> f64 { - self.width() * self.height() - } -} -``` - -The attribute argument has three parts, of which only the provider name is required. The leading **`new`** keyword is optional and, when present, tells the macro to also emit `pub struct RectangleArea;` so you need not declare the provider struct separately. The **provider name** (`RectangleArea` above) is the type that occupies the `Self` position in the generated provider impl. An optional **`: ComponentType`** suffix overrides the component name used in the generated [`IsProviderFor`](../traits/is_provider_for.md) impl; when omitted, the component defaults to the provider trait's name with a `Component` suffix, so implementing `AreaCalculator` targets `AreaCalculatorComponent`. - -The `for Context` clause is optional, and **omitting it is the preferred form** — the unqualified `impl AreaCalculator` is what makes a provider read like an ordinary trait impl. When you write `impl AreaCalculator` with no `for` and no leading context type parameter, the macro inserts a generic context parameter for you, using the reserved identifier `__Context__`. The block above is therefore equivalent to `impl<__Context__> AreaCalculator for __Context__`, with `self`/`Self` standing in for that inserted parameter. Name the context explicitly — `impl AreaCalculator for Context` — only when you genuinely need it: to bound the context in the impl generics in a way the sugar cannot express, such as a lifetime or higher-ranked bound, or to refer to it by a readable name. Both forms are accepted, but reach for the explicit one only when the implicit one falls short. - -A provider may be generic. Type parameters on the provider, such as a higher-order provider's inner provider, are written in the `Self` position of the attribute and in the impl generics together: - -```rust -#[cgp_impl(new ScaledAreaCalculator)] -#[use_provider(InnerCalculator: AreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - let base_area = InnerCalculator::area(self); - base_area * scale_factor * scale_factor - } -} -``` - -Several companion attributes are supported on a `#[cgp_impl]` block and are processed before the provider-trait rewrite. Method parameters marked [`#[implicit]`](../attributes/implicit.md) are extracted from the signature and turned into `HasField` reads on the context. [`#[uses(...)]`](../attributes/uses.md) adds simple trait bounds on `Self`, [`#[use_type(Trait.Type)]`](../attributes/use_type.md) imports an abstract type and rewrites its occurrences to fully qualified form, and [`#[use_provider(...)]`](../attributes/use_provider.md) supports higher-order providers by adding the `Self` parameter to an inner provider bound. Finally, [`#[default_impl(...)]`](../traits/default_namespace.md) registers the provider as a namespace's per-type default, emitting an extra delegation impl alongside the provider without changing the provider impl itself. - -## Syntax Grammar - -The attribute argument of `#[cgp_impl]` names the provider, optionally preceded by `new` and optionally followed by a component-type override: - -```ebnf -CgpImplArgs -> `new`? ProviderType ( `:` ComponentType )? - -ProviderType -> Type -ComponentType -> Type -``` - -The optional `new` keyword makes the macro also emit `pub struct ;`. `ProviderType` is the type that takes the `Self` position of the generated provider impl: a plain provider name, a generic provider such as `ScaledAreaCalculator`, or the literal `Self` for the bare-impl passthrough described in Expansion. The optional `: ComponentType` overrides the component used in the generated `IsProviderFor` impl, defaulting otherwise to the provider trait's name with a `Component` suffix. Both `ProviderType` and `ComponentType` are Rust `Type` productions. - -## Expansion - -`#[cgp_impl]` desugars to [`#[cgp_provider]`](cgp_provider.md): it moves the context type back to the leading position of the provider trait, swaps the provider name into the `Self` position, and rewrites every `self`/`Self` reference, then hands the result to the same machinery `#[cgp_provider]` uses. The clearest way to see this is a minimal example with the context named explicitly. Starting from: - -```rust -#[cgp_impl(new ValueToString)] -impl FooProvider for Context { - fn foo(&self, value: u32) -> String { - value.to_string() - } -} -``` - -the macro produces the provider impl, the `IsProviderFor` impl, and (because `new` was given) the provider struct: - -```rust -impl FooProvider for ValueToString { - fn foo(__context__: &Context, value: u32) -> String { - value.to_string() - } -} - -impl IsProviderFor for ValueToString {} - -pub struct ValueToString; -``` - -Three transformations happened here. The trait `FooProvider` gained `Context` as its leading type argument; the `Self` type of the impl changed from `Context` to the provider `ValueToString`; and the method receiver `&self` became the explicit parameter `__context__: &Context`. The receiver identifier is the snake-cased form of the context type wrapped in double underscores: both `Context` and the default `__Context__` become `__context__`. Every use of `self` in the body is rewritten to that identifier, while every use of `Self` is rewritten to the context type. - -This rewrite is scoped to the block's own method bodies. An item nested *inside* a body — a local `struct` with its own `impl`, a helper `fn`, an inline `trait` — introduces a fresh `self`/`Self` that names that item rather than the context, exactly as it would in ordinary Rust, so the macro leaves it untouched: a local `impl Display for Wrapper { fn fmt(&self, …) { … self.0 … } }` inside a provider method keeps its own `&self` and `self.0`. Closures, by contrast, capture the enclosing `self` and are rewritten like any other expression. The one exception is a nested item written inside a `macro!( … )` invocation, whose `self`/`Self` the token-level rewrite cannot scope and so rewrites anyway. - -When the `for Context` clause is omitted, the only difference is that the inserted context parameter is `__Context__`. The earlier `RectangleArea` example is equivalent to writing the context out by hand: - -```rust -#[cgp_impl(new RectangleArea)] -impl<__Context__> AreaCalculator for __Context__ -where - __Context__: HasDimensions, -{ - fn area(&self) -> f64 { - self.width() * self.height() - } -} -``` - -which desugars to: - -```rust -#[cgp_new_provider] -impl<__Context__> AreaCalculator<__Context__> for RectangleArea -where - __Context__: HasDimensions, -{ - fn area(__context__: &__Context__) -> f64 { - __context__.width() * __context__.height() - } -} -``` - -The generated `IsProviderFor` impl is a copy of the provider impl's signature with its body removed and the same `where` clause retained, so the provider's dependencies are captured for error reporting. The first argument is the component name, the second is the context type, and the third is a tuple of any remaining provider-trait type parameters. For a provider trait with multiple parameters, such as `ComputerRef`, the trailing parameters are grouped into that tuple — the `IsProviderFor` impl reads `IsProviderFor`. - -One special case bypasses the provider rewrite entirely. Writing `#[cgp_impl(Self)]` — naming `Self` as the provider — emits the `impl` block unchanged as an ordinary consumer-trait implementation on the concrete context. This requires the `for Context` clause to be present, and it is useful when you want to implement a consumer trait directly while still applying companion attributes such as [`#[use_provider]`](../attributes/use_provider.md). Because no provider struct is generated in this form, the `new` keyword and the `: ComponentType` override have no effect when the provider is `Self`: - -```rust -#[cgp_impl(Self)] -#[use_provider(RectangleArea: AreaCalculator)] -impl CanCalculateArea for Rectangle { - fn area(&self) -> f64 { - RectangleArea::area(self) - } -} -``` - -## Examples - -A complete provider written with `#[cgp_impl]`, using an implicit argument and read start-to-finish, shows the construct in its most idiomatic form. Given the `AreaCalculator` component from [`#[cgp_component]`](cgp_component.md): - -```rust -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} -``` - -The `#[implicit]` parameters are removed from the signature and replaced by reads of the `width` and `height` fields from the context, so the provider depends on `Self: HasField` and the matching `height` bound. The provider struct `RectangleArea` is defined by the `new` keyword. A concrete context then wires the component to this provider and uses it: - -```rust -#[derive(HasField)] -pub struct Rectangle { - pub width: f64, - pub height: f64, -} - -delegate_components! { - Rectangle { - AreaCalculatorComponent: RectangleArea, - } -} - -fn print_area(rect: &Rectangle) { - println!("area = {}", rect.area()); -} -``` - -The call `rect.area()` resolves through the consumer blanket impl to `Rectangle`'s table, which maps `AreaCalculatorComponent` to `RectangleArea`, and `RectangleArea::area` reads `rect.width` and `rect.height` to compute the result. - -## Related constructs - -`#[cgp_impl]` is the recommended way to implement a component defined by [`#[cgp_component]`](cgp_component.md), and it is one layer of sugar above [`#[cgp_provider]`](cgp_provider.md), which it desugars to; [`#[cgp_new_provider]`](cgp_new_provider.md) is the `new`-keyword equivalent at that lower layer. For the common case where only a single implementation is ever needed and no wiring is desired, [`#[cgp_fn]`](cgp_fn.md) is the lighter alternative. The companion attributes [`#[implicit]`](../attributes/implicit.md), [`#[uses]`](../attributes/uses.md), [`#[use_type]`](../attributes/use_type.md), [`#[use_provider]`](../attributes/use_provider.md), and [`#[default_impl]`](../traits/default_namespace.md) all apply inside a `#[cgp_impl]` block. Once a provider is written, [`delegate_components!`](delegate_components.md) wires it onto a context and [`check_components!`](check_components.md) verifies the wiring. - -## Source - -- Entry point: `cgp_impl` in [crates/macros/cgp-macro-lib/src/cgp_impl.rs](../../../crates/macros/cgp-macro-lib/src/cgp_impl.rs), which lowers the impl and emits the lowered provider impl alongside any default impls. -- Logic: [crates/macros/cgp-macro-core/src/types/cgp_impl/](../../../crates/macros/cgp-macro-core/src/types/cgp_impl/) — attribute argument parsing (the `new` keyword, provider type, optional component type) in `args.rs`; the lowering that extracts implicit arguments, applies companion attributes, and inserts the `__Context__` parameter when `for` is omitted in `item.rs`; the rewrite of `self`/`Self` and the handoff to `#[cgp_provider]` in `lowered.rs`; and the bare-impl passthrough for `#[cgp_impl(Self)]` in `provider_or_bare.rs`. -- `self`/`Self` rewriting: the `ReplaceSelfType`, `ReplaceSelfReceiver`, and `ReplaceSelfValue` visitors in [crates/macros/cgp-macro-core/src/visitors/](../../../crates/macros/cgp-macro-core/src/visitors/). -- Internal walkthrough (pipeline, generated items, corner cases, and the index of tests and snapshots): [implementation/entrypoints/cgp_impl.md](../../implementation/entrypoints/cgp_impl.md). diff --git a/docs/reference/macros/cgp_namespace.md b/docs/reference/macros/cgp_namespace.md deleted file mode 100644 index b5c3a94c..00000000 --- a/docs/reference/macros/cgp_namespace.md +++ /dev/null @@ -1,225 +0,0 @@ -# `#[cgp_namespace]` - -`cgp_namespace!` defines a *namespace* — a reusable, named lookup table that maps component keys to providers — so that a concrete context can inherit a whole group of wirings at once and still override individual entries. - -## Purpose - -`cgp_namespace!` exists to make groups of component wirings reusable across contexts. With [`delegate_components!`](delegate_components.md) alone, every context spells out its own table entry by entry; two contexts that should share the same wiring must repeat it. A namespace lifts that table out of any single context and gives it a name, turning "this exact set of providers" into a thing other contexts can refer to and build on. - -The mechanism that makes this work is a layer of indirection between a context's delegation table and the actual providers. A namespace is not itself a context; it is a trait (named after the namespace) carrying a `Delegate` associated type, implemented per key. A context that opts into a namespace forwards every lookup through that trait, so the namespace's entries become the context's defaults. The forwarding is keyed by a *path* — a type-level list of symbols and component names — rather than by a bare component name, which is what lets one namespace inherit from another and lets a context shadow a single inherited entry without disturbing the rest. - -The payoff is preset-style configuration with selective override. A context can say "use everything in this namespace" and then add a handful of its own entries that win over the inherited ones, because a directly-wired entry on the context resolves before the namespace fallback is consulted. This is the same inheritance-with-override pattern presets rely on, expressed entirely through the trait system with no runtime cost. - -## Syntax - -`cgp_namespace!` is a function-like macro whose body resembles a `delegate_components!` table with an optional namespace header. The simplest form defines a fresh namespace with `new` and lists entries that map component keys to redirect paths: - -```rust -cgp_namespace! { - new MyNamespace { - FooProviderComponent => - @MyFooComponent, - } -} -``` - -The `new` keyword tells the macro to also emit the namespace's marker struct and its lookup trait; omit it only when those are already declared elsewhere. `MyNamespace` is the namespace name, which becomes both a trait and (with `new`) a backing struct. The entries inside the braces are the namespace's wiring. - -Two distinct entry forms appear in the body, and they generate different table contents. A `=>` entry redirects a key to a path: `FooProviderComponent => @MyFooComponent` says "when this namespace is asked for `FooProviderComponent`, look up the path `@MyFooComponent` instead." A `:` entry maps a key directly to a provider, as in `delegate_components!`: `[String, u64]: ShowWithDisplay` makes the namespace resolve those keys straight to the `ShowWithDisplay` provider. Paths written with the `@` sigil — `@MyFooComponent`, `@app.ErrorRaiserComponent`, `@cgp.core.error` — are dotted sequences of symbols and type names that desugar into type-level path lists. - -A namespace can inherit from a parent namespace by naming it after a colon in the header: - -```rust -cgp_namespace! { - new ExtendedNamespace: DefaultNamespace { - @cgp.core.error => - @app, - } -} -``` - -Here `ExtendedNamespace` inherits every entry of `DefaultNamespace` and additionally rewrites the `@cgp.core.error` path prefix to `@app`. The parent may itself be parameterized (it is parsed as a path with type arguments), and the entries in the child body layer on top of the inherited ones. - -Defining a namespace is only half of the pattern; a context joins a namespace through `delegate_components!` using a `namespace` header line, and individual components attach to a namespace through the `#[prefix(...)]` attribute on their trait. Those two constructs are where namespaces are consumed, and both are shown under Expansion and Examples below. - -## Syntax Grammar - -The body of `cgp_namespace!` is an optional generic list and `new` keyword, a namespace name, an optional parent namespace, and a brace-delimited table: - -```ebnf -CgpNamespace -> Generics? `new`? NamespaceName ( `:` ParentNamespace )? `{` NamespaceBody `}` - -NamespaceName -> IDENTIFIER GenericArgs? -ParentNamespace -> TypePath GenericArgs? - -NamespaceBody -> Statement* ( Mapping ( `,` Mapping )* `,`? )? -``` - -The mappings in `NamespaceBody` are the same `Mapping` production as [`delegate_components!`](delegate_components.md) — most often the `` `=>` `` redirect to an `@`-`Path` or a `` `:` `` direct provider. The `` `:` `` between `NamespaceName` and `ParentNamespace` is the inheritance colon, distinct from a mapping's `:`. `NamespaceName` is an identifier with optional generic arguments (it becomes both a trait and, with `new`, a struct); `ParentNamespace` is a type path that may itself be parameterized. - -This macro also owns the two namespace statement forms that a context's [`delegate_components!`](delegate_components.md) table uses to join a namespace: - -```ebnf -Statement -> NamespaceStmt | ForStmt - -NamespaceStmt -> `namespace` IDENTIFIER `;` - -ForStmt -> `for` `<` IDENTIFIER `,` IDENTIFIER `>` `in` TypePath WhereClause? - `{` ( NormalMapping ( `,` NormalMapping )* `,`? )? `}` - -NormalMapping -> Key `:` ProviderValue -``` - -A `NamespaceStmt` forwards every lookup on the table through the named namespace. A `ForStmt` binds a key variable and a provider variable, reads each entry of the table named after `in`, and emits one mapping per entry — its body holds only `` `:` `` mappings (`NormalMapping`), whose `Key` and `ProviderValue` are the shared productions from [`delegate_components!`](delegate_components.md). Its optional `WhereClause` is merged into every impl the loop generates, so a bound written there (`for in Table where T: Clone { … }`) constrains which keys the loop wires, alongside the namespace bound the loop reconstructs. `TypePath` and `WhereClause` are Rust grammar productions. - -Like [`delegate_components!`](delegate_components.md), the body accepts no attributes on any entry — on a mapping key, a `=>` redirect key, or a key inside a `for` loop — and rejects any it finds with a spanned "unsupported attribute" error rather than silently discarding it. - -## Expansion - -`cgp_namespace!` emits, in order, an optional marker struct, an optional lookup trait, and one `impl` of that trait per entry (plus one inheritance `impl` when a parent is named). Take the `new` namespace with a single redirect entry: - -```rust -cgp_namespace! { - new MyNamespace { - FooProviderComponent => - @MyFooComponent, - } -} -``` - -Because `new` is present, the macro first emits a backing struct whose name is the namespace name wrapped in `__…Components`, then the lookup trait. The trait carries the table's generic `__Table__` parameter and a single `Delegate` associated type: - -```rust -pub struct __MyNamespaceComponents; - -pub trait MyNamespace<__Table__> { - type Delegate; -} -``` - -Each `=>` entry becomes an `impl` of that trait for the entry's key, whose `Delegate` is a [`RedirectLookup`](../providers/redirect_lookup.md) pointing the table at the entry's path. The `@MyFooComponent` path desugars into a `PathCons<…, Nil>` type-level list: - -```rust -impl<__Table__> MyNamespace<__Table__> for FooProviderComponent { - type Delegate = RedirectLookup<__Table__, PathCons>; -} -``` - -Reading this back: `MyNamespace<__Table__>::Delegate` for the key `FooProviderComponent` is "look up the path `MyFooComponent` inside whatever table `__Table__` is." `RedirectLookup` is a CGP-defined provider that resolves by delegating `Components` along `Path`; the namespace never names a concrete provider for this key, it only re-routes the lookup, so the actual provider is decided wherever the path eventually lands. - -A `:` entry instead maps the key directly to the named provider, with no `RedirectLookup` indirection. From the array form `[String, u64]: ShowWithDisplay`, the macro emits one `impl` per key: - -```rust -impl<__Table__> DefaultShowComponents<__Table__> for String { - type Delegate = ShowWithDisplay; -} -impl<__Table__> DefaultShowComponents<__Table__> for u64 { - type Delegate = ShowWithDisplay; -} -``` - -When a parent namespace is named, the macro prepends one extra blanket `impl` that forwards unmatched keys to the parent. For `new ExtendedNamespace: DefaultNamespace { … }`, the inherited entries arrive through this impl: - -```rust -impl<__Table__, __Key__, __Value__> ExtendedNamespace<__Table__> for __Key__ -where - __Key__: DefaultNamespace<__ExtendedNamespaceComponents>, - __Key__: DefaultNamespace<__Table__, Delegate = __Value__>, -{ - type Delegate = __Value__; -} -``` - -This says: for any `__Key__` the parent `DefaultNamespace` resolves, `ExtendedNamespace` resolves it to the same `__Value__`. The body entries of the child are emitted after this blanket impl and take precedence where their keys are more specific. The path-rewriting entry `@cgp.core.error => @app` becomes an impl keyed on the `cgp.core.error` path prefix whose `Delegate` is a `RedirectLookup` onto the `@app` prefix — rerouting an entire subtree of the parent's namespace rather than a single component. - -The other half of the pattern is what attaches a component to a namespace, via the `#[prefix(...)]` attribute on the component's trait. Given: - -```rust -#[cgp_component(BarProvider)] -#[prefix(@MyBarComponent in MyNamespace)] -pub trait Bar { - fn bar(&self); -} -``` - -`#[cgp_component]` emits its usual items, and `#[prefix]` adds one extra impl that registers `BarProviderComponent` into `MyNamespace` under the prefix path `@MyBarComponent`: - -```rust -impl<__Components__> MyNamespace<__Components__> for BarProviderComponent { - type Delegate = RedirectLookup< - __Components__, - PathCons>, - >; -} -``` - -So `MyNamespace`, asked for `BarProviderComponent`, redirects the lookup to the path `MyBarComponent → BarProviderComponent`. A component may carry several `#[prefix]` attributes to register itself into several namespaces at once. - -Two details of the expansion are worth holding onto. The table parameter is literally named `__Table__` and the inheritance blanket impl uses `__Key__`/`__Value__`; the examples keep those names because they appear verbatim in compiler errors. And every path under `@` becomes a `PathCons`/`Symbol`/`Chars` type-level list — `@my_app.MyFooComponent` expands to `PathCons>, PathCons>`, with dotted lowercase segments becoming `Symbol` string literals and capitalized segments becoming the named type. - -## Examples - -A namespace becomes useful once a context joins it and overrides part of it. Start with a namespace that supplies default per-type providers, defined with `new`: - -```rust -use cgp::prelude::*; - -cgp_namespace! { - new DefaultShowComponents { - [String, u64]: ShowWithDisplay, - } -} -``` - -A context then opts into a namespace inside `delegate_components!` with a `namespace` header line, and may add its own entries that win over the namespace defaults. Joining `DefaultNamespace` and pulling defaults in through a `for` loop over `DefaultShowComponents`: - -```rust -pub struct AppB; - -delegate_components! { - AppB { - namespace DefaultNamespace; - - for in DefaultShowComponents { - @test.ShowImplComponent.T: Provider, - } - } -} -``` - -The `namespace DefaultNamespace;` line makes `AppB` forward every component lookup through `DefaultNamespace`, and the `for … in DefaultShowComponents` block wires `AppB`'s `ShowImplComponent` entries by reading `DefaultShowComponents`'s `Delegate` for each type `T`. To override a single entry, a later direct line on the same context simply names a different provider for that key; because the context's own entry resolves before the namespace fallback, it shadows the inherited one without touching the others: - -```rust -delegate_components! { - AppA { - namespace DefaultNamespace; - - for in DefaultImpls1 { - @test.ShowImplComponent.T: Provider, - } - - @test.ShowImplComponent.u64: - ShowWithDisplay, // overrides the inherited entry for u64 - } -} -``` - -Inheritance composes the same way at the namespace level: `ExtendedNamespace: DefaultNamespace` produces a namespace that resolves everything `DefaultNamespace` does, plus the child's own entries, and any context joining `ExtendedNamespace` gets the merged result. - -## Related constructs - -`cgp_namespace!` sits between component definitions and context wiring, so it relates to constructs on both sides. [`#[cgp_component]`](cgp_component.md) defines the components whose keys a namespace maps, and its [`#[prefix(...)]`](cgp_component.md) attribute is what registers a component into a namespace under a path. [`delegate_components!`](delegate_components.md) is where a context joins a namespace (via its `namespace` header) and where individual overrides are written; [`delegate_and_check_components!`](delegate_and_check_components.md) does the same and additionally checks the entries written directly in the block, though its derivation does not cover the components inherited through the namespace, so verifying the full merged wiring is left to a standalone [`check_components!`](check_components.md). The namespace's `Delegate` entries are resolved through [`RedirectLookup`](../providers/redirect_lookup.md), and per-type defaults are commonly expressed through [`use_delegate`](../providers/use_delegate.md)-style dispatch and the `DefaultNamespace` / `DefaultImpls1` traits in `cgp-component`. The underlying per-key table machinery is [`DelegateComponent`](../traits/delegate_component.md), which `RedirectLookup` walks at resolution time. - -## Known issues - -A context that joins a namespace with `namespace N;` cannot also wire, directly on itself, a path that `N` already registers. The `namespace N;` header emits a blanket `impl DelegateComponent for Ctx where Key: N`, which already covers every path `N` resolves; a direct `@path: Provider` entry on the same context emits a second `DelegateComponent` impl for that path, and the compiler rejects the overlap with `E0119`. Overriding therefore works only on a path the namespace routes *to* but does not itself terminate: register the component's [`#[prefix]`](cgp_component.md) redirect in a base namespace the context inherits, and leave the leaf path unclaimed by the namespace so the context can supply it. A path the namespace registers with a `:` body entry or a `#[default_impl]` is not overridable on the context; change it in the namespace instead. The same restriction stops an inheriting namespace from redefining a key its parent binds. This is a whole-program coherence fact the macro cannot detect, so it is deferred to the compiler; its full anatomy is in the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md) error class. A related overlap arises when a context emits *two* blanket forwardings — joining two namespaces, or a bare-key `for` loop (`for in Table { Key: Value }`) alongside a `namespace` join, which is why a loop key must be embedded in a path (`@app.SomeComponent.Key: Value`) — the [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md) error class. - -Two further whole-program failures are deferred to the compiler the same way. If a component is routed into a joined namespace by a `#[prefix]` but no entry ever *binds* a provider at its path — no `#[default_impl]`, body entry, or direct wiring — the redirect lands on an empty table slot, and a `check_components!` reports the lookup as unsatisfied (`E0277`); this is the [unregistered namespace path](../../errors/checks/unregistered-namespace-path.md) error class. And a circular parent chain — `new A: B` with `new B: A`, or a self-inheriting `new A: A` — makes the inheritance blanket impl's `where` clause loop, which the compiler rejects eagerly at the `cgp_namespace!` definitions with an `E0275` overflow; this is the [namespace inheritance cycle](../../errors/wiring/namespace-inheritance-cycle.md) error class. - -## Source - -- Entry point: `cgp_namespace` in [crates/macros/cgp-macro-lib/src/cgp_namespace.rs](../../../crates/macros/cgp-macro-lib/src/cgp_namespace.rs), which parses a `NamespaceTable` and calls `.eval()`. -- Logic: [crates/macros/cgp-macro-core/src/types/namespace/](../../../crates/macros/cgp-macro-core/src/types/namespace/) — `table.rs` parses the header (`new`, namespace name, optional `: parent`) and builds the trait, struct, per-entry impls, and the parent-inheritance impl; `inherit.rs` builds the path-rewriting inheritance entry; `eval.rs` holds the emitted `EvaluatedNamespaceTable`. -- `#[prefix(...)]` attribute (attaches a component to a namespace): parsed in [crates/macros/cgp-macro-core/src/types/attributes/prefix.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/prefix.rs); the matching `RedirectLookup` provider impl is emitted by [crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_redirect_lookup_impl.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_redirect_lookup_impl.rs). -- Runtime traits: `DefaultNamespace`/`DefaultImpls1`/`DefaultImpls2` in [crates/core/cgp-component/src/namespaces.rs](../../../crates/core/cgp-component/src/namespaces.rs) and `RedirectLookup` in [crates/core/cgp-component/src/providers/redirect_lookup.rs](../../../crates/core/cgp-component/src/providers/redirect_lookup.rs). -- Internal walkthrough (the pipeline, the item each entry form generates, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/cgp_namespace.md](../../implementation/entrypoints/cgp_namespace.md). diff --git a/docs/reference/macros/cgp_new_provider.md b/docs/reference/macros/cgp_new_provider.md deleted file mode 100644 index fd161717..00000000 --- a/docs/reference/macros/cgp_new_provider.md +++ /dev/null @@ -1,120 +0,0 @@ -# `#[cgp_new_provider]` - -`#[cgp_new_provider]` behaves exactly like [`#[cgp_provider]`](cgp_provider.md) but additionally declares the provider struct, so a provider trait implementation and its `Self` type can be defined in one place. - -## Purpose - -`#[cgp_new_provider]` exists to save the one line of boilerplate that almost always accompanies a fresh provider: the `pub struct ProviderName;` declaration. A provider struct is a type-level-only marker — it is never instantiated and holds no runtime state — so declaring it separately from the impl that gives it meaning is pure ceremony. `#[cgp_new_provider]` folds that declaration into the impl, producing the struct, the provider impl, and the generated [`IsProviderFor`](../traits/is_provider_for.md) impl together. - -Use `#[cgp_new_provider]` when you are introducing a new provider; use [`#[cgp_provider]`](cgp_provider.md) when the struct already exists, for example because it is declared with default generic parameters that the attribute form cannot express, or because several impls share one struct. - -## Syntax - -`#[cgp_new_provider]` is applied to a provider-trait impl and accepts the same optional component-type argument as [`#[cgp_provider]`](cgp_provider.md). The only requirement beyond `#[cgp_provider]` is that the provider struct must *not* already be declared, since the macro declares it: - -```rust -#[cgp_new_provider] -impl AreaCalculator for RectangleArea -where - Context: HasDimensions, -{ - fn area(context: &Context) -> f64 { - context.width() * context.height() - } -} -``` - -As with `#[cgp_provider]`, the impl uses the native provider-trait shape — an explicit leading `Context` type parameter, the provider struct in the `Self` position, and methods taking `context: &Context` rather than `&self`. An optional argument overrides the component type used in the `IsProviderFor` impl, defaulting otherwise to the provider trait's name plus a `Component` suffix. - -## Syntax Grammar - -The attribute argument of `#[cgp_new_provider]` is the same single optional component type as [`#[cgp_provider]`](cgp_provider.md): - -```ebnf -CgpNewProviderArgs -> ComponentType? - -ComponentType -> Type -``` - -The argument behaves exactly as it does for `#[cgp_provider]` — omitted means the component defaults to the provider trait's name plus a `Component` suffix, and a given `Type` overrides it. The struct declaration that distinguishes this macro is implied by the macro name and is not written in the argument. - -## Expansion - -`#[cgp_new_provider]` is implemented as `#[cgp_provider]` with the `new` keyword forced on; its expansion is therefore the `#[cgp_provider]` expansion plus a struct declaration. The example above produces: - -```rust -impl AreaCalculator for RectangleArea -where - Context: HasDimensions, -{ - fn area(context: &Context) -> f64 { - context.width() * context.height() - } -} - -impl IsProviderFor for RectangleArea -where - Context: HasDimensions, -{} - -pub struct RectangleArea; -``` - -The provider impl and the derived `IsProviderFor` impl are exactly what [`#[cgp_provider]`](cgp_provider.md) emits — see its Expansion section for how the component type, context type, and `Params` tuple are assembled. The one addition is `pub struct RectangleArea;`. - -The struct's shape is taken from the `Self` type of the impl. A plain provider name yields a unit struct as above. A generic provider yields a struct with a `PhantomData` field over its parameters, so the parameters are bound. For instance: - -```rust -#[cgp_new_provider] -impl Runner for SpawnAndRun -where - Context: 'static + Send + Clone + CanSendRun, -{ /* ... */ } -``` - -emits, in addition to the provider and `IsProviderFor` impls: - -```rust -pub struct SpawnAndRun(pub ::core::marker::PhantomData); -``` - -## Examples - -Defining a complete provider in one block, struct included, is the common idiom for an implementation that has no struct yet: - -```rust -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_auto_getter] -pub trait HasDimensions { - fn width(&self) -> &f64; - fn height(&self) -> &f64; -} - -#[cgp_new_provider] -impl AreaCalculator for RectangleArea -where - Context: HasDimensions, -{ - fn area(context: &Context) -> f64 { - context.width() * context.height() - } -} -``` - -This is equivalent to writing `pub struct RectangleArea;` followed by the same impl annotated with [`#[cgp_provider]`](cgp_provider.md). In most code, the same provider would be written even more concisely with [`#[cgp_impl(new RectangleArea)]`](cgp_impl.md), whose `new` keyword plays the identical role of declaring the struct while also letting the body use `self`/`Self`. - -## Related constructs - -`#[cgp_new_provider]` is [`#[cgp_provider]`](cgp_provider.md) plus a struct declaration, and it implements a provider trait generated by [`#[cgp_component]`](cgp_component.md). Its closest relative is [`#[cgp_impl]`](cgp_impl.md) with the `new` keyword, which produces the same three items — struct, provider impl, and `IsProviderFor` impl — from consumer-trait-style syntax; `#[cgp_impl(new ...)]` desugars to `#[cgp_new_provider]`. A provider so defined is wired to a context with [`delegate_components!`](delegate_components.md) and checked with [`check_components!`](check_components.md). - -## Source - -- Entry point: `cgp_new_provider` in [crates/macros/cgp-macro-lib/src/cgp_new_provider.rs](../../../crates/macros/cgp-macro-lib/src/cgp_new_provider.rs); it parses the same `ProviderArgs`, sets `new` to enabled, and then runs the identical lowering as [`#[cgp_provider]`](cgp_provider.md). -- Generation logic (including the struct declaration emitted when `new` is set): [crates/macros/cgp-macro-core/src/types/cgp_provider/](../../../crates/macros/cgp-macro-core/src/types/cgp_provider/); the struct shape is built in `item.rs` (`to_provider_struct`). -- Internal walkthrough (pipeline, generated items, corner cases, and the index of tests and snapshots): [implementation/entrypoints/cgp_new_provider.md](../../implementation/entrypoints/cgp_new_provider.md). diff --git a/docs/reference/macros/cgp_producer.md b/docs/reference/macros/cgp_producer.md deleted file mode 100644 index 5d5a7213..00000000 --- a/docs/reference/macros/cgp_producer.md +++ /dev/null @@ -1,123 +0,0 @@ -# `#[cgp_producer]` - -`#[cgp_producer]` is an attribute macro that turns a no-argument function into a [`Producer`](../components/producer.md) provider, generating the provider struct, the provider impl, and the wiring that makes the produced value flow out of every handler shape. - -## Purpose - -`#[cgp_producer]` exists for the degenerate handler that takes no input at all: a computation that yields a value from nothing, such as a constant or a value drawn from the context. It is the input-less sibling of [`#[cgp_computer]`](cgp_computer.md), and like that macro it lets the author write a plain function and have the macro synthesize the provider struct and impl, sparing them the handler plumbing. Where `#[cgp_computer]` defines a [`Computer`](../components/computer.md), `#[cgp_producer]` defines a [`Producer`](../components/producer.md) — the handler-family member whose method takes only the context and `Code`, with no input value. - -Because a producer can stand in for any handler — a handler that ignores its input is just a producer with an unused parameter — the macro wires the generated producer into every member of the handler family. A single `#[cgp_producer]` function therefore answers `produce`, `compute`, `try_compute`, `compute_async`, `handle`, and all the `…Ref` variants, every one of them yielding the same produced value. - -## Syntax - -`#[cgp_producer]` is applied to a free function and accepts an optional provider name as its argument: - -```rust -#[cgp_producer] -fn magic_number() -> u64 { - 42 -} - -#[cgp_producer(TheAnswer)] -fn magic_number() -> u64 { - 42 -} -``` - -When the argument is omitted the provider struct takes the function name in PascalCase — `magic_number` becomes `MagicNumber` — and when given it is used verbatim. The function's return type becomes the producer's output. The macro constrains the function tightly to match what a producer can be: it must have no parameters (a producer takes no input and no `self` receiver), it must not be `async` (the producer trait is synchronous), and it must have no generic parameters. Violating any of these is a compile error pointing at the offending part of the signature. - -## Syntax Grammar - -The attribute argument of `#[cgp_producer]` is a single optional provider name: - -```ebnf -CgpProducerArgs -> ProviderName? - -ProviderName -> IDENTIFIER -``` - -When the argument is omitted, the generated provider struct takes the function name converted to PascalCase; a given `IDENTIFIER` is used verbatim. The annotated function is plain Rust, but the macro constrains it to a producer's shape — no parameters, no `async`, and no generic parameters — as described in Syntax above. - -## Expansion - -The macro emits three items: the original function unchanged, a `#[cgp_new_provider]` impl of the [`Producer`](../components/producer.md) trait that calls the function, and a `delegate_components!` block wiring the whole handler family to the [`PromoteProducer`](../providers/handler_combinators.md) bundle. Given - -```rust -#[cgp_producer] -pub fn magic_number() -> u64 { - 42 -} -``` - -the macro expands to the function plus: - -```rust -#[cgp_new_provider] -impl<__Context__, __Code__> Producer<__Context__, __Code__> for MagicNumber { - type Output = u64; - - fn produce(_context: &__Context__, _code: PhantomData<__Code__>) -> Self::Output { - magic_number() - } -} - -delegate_components! { - MagicNumber { - [ - ComputerComponent, - ComputerRefComponent, - TryComputerComponent, - TryComputerRefComponent, - AsyncComputerComponent, - AsyncComputerRefComponent, - HandlerComponent, - HandlerRefComponent, - ]: - PromoteProducer, - } -} -``` - -The `#[cgp_new_provider]` attribute defines the `MagicNumber` struct and its `IsProviderFor` impl alongside the `Producer` impl. The context and code generic parameters are introduced under the reserved names `__Context__` and `__Code__`; the `produce` method ignores both and simply calls the function. The `delegate_components!` block then routes all eight handler components to [`PromoteProducer`](../providers/handler_combinators.md), the promotion bundle built for a producer base. That bundle wires `ComputerComponent` to `Promote` — which discards the computer's input and calls `produce` — and derives the remaining members through `PromoteComputer`, so every handler shape emits the produced value regardless of any input it is handed. - -Unlike `#[cgp_computer]`, the expansion has no variation. Because the function cannot be async, cannot have generics, and is not analyzed for a `Result` return, there is a single base trait and a single promotion bundle for every `#[cgp_producer]` function — the producer's output type is taken exactly as written, whether or not it happens to be a `Result`. - -## Examples - -A self-contained use defines a producer and wires a minimal context, then reads the same value out through every handler shape: - -```rust -use cgp::prelude::*; -use cgp::extra::handler::{Producer, Computer, TryComputer, Handler}; - -#[cgp_producer] -pub fn magic_number() -> u64 { - 42 -} - -pub struct App; - -delegate_components! { - App { - ErrorTypeProviderComponent: UseType, - } -} - -// The single `magic_number` definition answers every shape, all yielding 42: -// MagicNumber::produce(&App, PhantomData::<()>) == 42 -// MagicNumber::compute(&App, PhantomData::<()>, &()) == 42 -// MagicNumber::try_compute(&App, PhantomData::<()>, &()) == Ok(42) -// MagicNumber::handle(&App, PhantomData::<()>, &()) resolves to Ok(42) -``` - -The computer and handler forms accept an input argument and ignore it, since the underlying producer takes none. The error type wired into `App` is what lets the fallible shapes (`try_compute`, `handle`) form their `Result`; the produced value is always returned as `Ok`. - -## Related constructs - -`#[cgp_producer]` defines a provider for the [`Producer`](../components/producer.md) component, part of the handler family described in [handlers](../../concepts/handlers.md). It is the input-less counterpart of [`#[cgp_computer]`](cgp_computer.md), which defines a [`Computer`](../components/computer.md) from a function with parameters, and both are handler-world analogues of [`#[cgp_fn]`](cgp_fn.md). The generated impl is emitted through [`#[cgp_new_provider]`](cgp_new_provider.md), and the producer is lifted into the full handler family by the [`PromoteProducer`](../providers/handler_combinators.md) bundle wired through [`delegate_components!`](delegate_components.md). - -## Source - -- Entrypoint: [crates/macros/cgp-extra-macro/src/lib.rs](../../../crates/macros/cgp-extra-macro/src/lib.rs), forwarding to the implementation in [crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs](../../../crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_producer.rs). -- `Producer` trait: [crates/extra/cgp-handler/src/components/produce.rs](../../../crates/extra/cgp-handler/src/components/produce.rs); the `PromoteProducer` bundle in [crates/extra/cgp-handler/src/providers/promote_all.rs](../../../crates/extra/cgp-handler/src/providers/promote_all.rs). -- Internal walkthrough (the signature validation, the generated items, and the index of behavioral tests): [implementation/entrypoints/cgp_producer.md](../../implementation/entrypoints/cgp_producer.md). diff --git a/docs/reference/macros/cgp_provider.md b/docs/reference/macros/cgp_provider.md deleted file mode 100644 index 1e1dba36..00000000 --- a/docs/reference/macros/cgp_provider.md +++ /dev/null @@ -1,144 +0,0 @@ -# `#[cgp_provider]` - -`#[cgp_provider]` is applied to a provider-trait implementation written directly on a named provider struct, and it auto-generates the matching [`IsProviderFor`](../traits/is_provider_for.md) impl from that implementation's `where` clause. - -## Purpose - -`#[cgp_provider]` exists to remove the one piece of boilerplate that every hand-written provider impl would otherwise have to repeat: the `IsProviderFor` marker impl. CGP requires that, alongside a provider's implementation of a provider trait, the provider also implement `IsProviderFor` under exactly the same constraints. This marker is what lets the compiler produce a readable error — naming the missing dependency — when a context's wiring is incomplete, instead of a terse "trait not implemented" message. Writing it by hand means duplicating the impl's generic parameters and entire `where` clause, and keeping the two copies in sync forever. - -`#[cgp_provider]` writes that second impl for you. You write only the real provider impl — `impl AreaCalculator for RectangleArea where ...` — and the macro emits a copy of it with the body stripped, the trait swapped to `IsProviderFor`, and the same `where` clause preserved. The dependencies are captured automatically and can never drift out of sync, because they are derived from the impl rather than restated. - -This is the form to reach for when you are working in the provider trait's native vocabulary — implementing the provider trait directly, with an explicit `Context` type parameter and a static-method signature that takes `context: &Context` rather than `&self`. It is the lower-level counterpart to [`#[cgp_impl]`](cgp_impl.md), which presents the same implementation in consumer-trait clothing and then desugars down to `#[cgp_provider]`. - -## Syntax - -`#[cgp_provider]` is applied to an `impl` block that implements a provider trait for a provider struct, and its attribute argument is an optional component type: - -```rust -#[cgp_provider] -impl AreaCalculator for RectangleArea -where - Context: HasDimensions, -{ - fn area(context: &Context) -> f64 { - context.width() * context.height() - } -} -``` - -The impl header is a normal provider-trait impl. The provider trait carries an explicit leading `Context` type parameter, the `Self` type is the provider struct (`RectangleArea`), and methods take the context as an ordinary parameter rather than as a `self` receiver. The provider struct must already exist; `#[cgp_provider]` does not define it. Use [`#[cgp_new_provider]`](cgp_new_provider.md) when you want the struct declared for you. - -The attribute takes one optional argument, the **component type** used in the generated `IsProviderFor` impl. When omitted, the component defaults to the provider trait's name with a `Component` suffix, so implementing `AreaCalculator` targets `AreaCalculatorComponent`. Pass the component explicitly when the provider trait's name does not follow that convention or when a provider implements a trait under a differently named component: - -```rust -#[cgp_provider(RunnerComponent)] -impl Runner for RunWithFooBar -where - Context: CanFetchFoo + CanFetchBar + CanRunFooBar, -{ - fn run(context: &Context, _code: PhantomData) -> Result<(), Context::Error> { - /* ... */ - } -} -``` - -## Syntax Grammar - -The attribute argument of `#[cgp_provider]` is a single optional component type: - -```ebnf -CgpProviderArgs -> ComponentType? - -ComponentType -> Type -``` - -When the argument is omitted the component defaults to the provider trait's name with a `Component` suffix; when present, that `Type` is substituted into the first position of the generated `IsProviderFor` impl. `Type` is the Rust type production. - -## Expansion - -`#[cgp_provider]` emits two items: the provider impl, passed through unchanged, and an `IsProviderFor` impl derived from it. Starting from: - -```rust -#[cgp_provider] -impl ComputerRef for FirstNameToString -where - Context: HasField, -{ - type Output = String; - - fn compute_ref(context: &Context, _code: PhantomData, _input: &Input) -> String { - context.get_field(PhantomData).to_string() - } -} -``` - -the macro produces: - -```rust -impl ComputerRef for FirstNameToString -where - Context: HasField, -{ - type Output = String; - - fn compute_ref(context: &Context, _code: PhantomData, _input: &Input) -> String { - context.get_field(PhantomData).to_string() - } -} - -impl IsProviderFor - for FirstNameToString -where - Context: HasField, -{} -``` - -The derived impl is the original impl with its body and associated types removed and its trait replaced. It keeps the same generic parameters (`Context, Code, Input`) and the same `where` clause, so it holds under precisely the conditions that the provider impl holds. Its trait arguments are assembled from the provider trait's arguments: the first is the **component type** (`ComputerRefComponent`, the default derived from the `ComputerRef` trait name); the second is the **context type**, taken from the provider trait's leading type argument (`Context`); and the third is the **`Params` tuple** holding every remaining provider-trait type parameter — here `(Code, Input)`. For a provider trait with no extra parameters beyond the context, the `Params` tuple is the empty `()`. - -The component-type argument is the only thing the attribute argument changes. Passing `#[cgp_provider(RunnerComponent)]` substitutes that type into the first position of the `IsProviderFor` impl in place of the default `{Trait}Component`; everything else about the expansion is unchanged. - -## Examples - -A self-contained provider for the `AreaCalculator` component, with its struct declared separately and its `IsProviderFor` impl generated, shows the construct in context: - -```rust -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_auto_getter] -pub trait HasDimensions { - fn width(&self) -> &f64; - fn height(&self) -> &f64; -} - -pub struct RectangleArea; - -#[cgp_provider] -impl AreaCalculator for RectangleArea -where - Context: HasDimensions, -{ - fn area(context: &Context) -> f64 { - context.width() * context.height() - } -} -``` - -The macro expands this into the provider impl above plus `impl IsProviderFor for RectangleArea where Context: HasDimensions {}`. A concrete context wires the component to `RectangleArea` exactly as it would for any provider, through [`delegate_components!`](delegate_components.md), and the `IsProviderFor` impl ensures that a context missing the `HasDimensions` dependency produces an error naming that dependency rather than an opaque one. - -In most code, the same provider would be written more concisely with [`#[cgp_impl]`](cgp_impl.md), which lets the body use `self`/`Self` and omit the explicit `Context` parameter. `#[cgp_provider]` is the right choice when you prefer to work directly in the provider trait's own form, or when reading code that another tool or macro has already lowered to that form. - -## Related constructs - -`#[cgp_provider]` implements a provider trait generated by [`#[cgp_component]`](cgp_component.md). It is the lower-level form that [`#[cgp_impl]`](cgp_impl.md) desugars to; prefer `#[cgp_impl]` for new code and reach for `#[cgp_provider]` when working in the native provider-trait shape. [`#[cgp_new_provider]`](cgp_new_provider.md) behaves identically but also declares the provider struct. The generated [`IsProviderFor`](../traits/is_provider_for.md) impl is the same marker that [`check_components!`](check_components.md) relies on to verify wiring, and a provider is connected to a context through [`delegate_components!`](delegate_components.md). - -## Source - -- Entry point: `cgp_provider` in [crates/macros/cgp-macro-lib/src/cgp_provider.rs](../../../crates/macros/cgp-macro-lib/src/cgp_provider.rs), which parses the optional component argument, lowers the impl, and emits the result. -- Logic: [crates/macros/cgp-macro-core/src/types/cgp_provider/](../../../crates/macros/cgp-macro-core/src/types/cgp_provider/) — attribute argument parsing (the optional component type) in `args.rs`; the lowering that derives the component default, the provider struct, and the `IsProviderFor` impl in `item.rs`; the emitted-token assembly in `lower.rs`; and the splitting of provider-trait arguments into context and `Params` tuple in `provider_impl_args.rs`. -- `IsProviderFor` derivation: [crates/macros/cgp-macro-core/src/types/provider_impl.rs](../../../crates/macros/cgp-macro-core/src/types/provider_impl.rs). -- Internal walkthrough (pipeline, generated items, corner cases, and the index of tests and snapshots): [implementation/entrypoints/cgp_provider.md](../../implementation/entrypoints/cgp_provider.md). diff --git a/docs/reference/macros/cgp_type.md b/docs/reference/macros/cgp_type.md deleted file mode 100644 index a5ff6515..00000000 --- a/docs/reference/macros/cgp_type.md +++ /dev/null @@ -1,134 +0,0 @@ -# `#[cgp_type]` - -`#[cgp_type]` defines an abstract-type component — a trait carrying a single associated type — by extending [`#[cgp_component]`](cgp_component.md) and generating the extra constructs that let a context choose the concrete type through wiring, most notably a [`UseType`](../providers/use_type.md) blanket impl. - -## Purpose - -`#[cgp_type]` exists to make associated types swappable across contexts the same way `#[cgp_component]` makes behavior swappable. An abstract type in CGP is just a trait with one associated type — `trait HasScalarType { type Scalar; }` — that lets generic code refer to `Self::Scalar` without committing to a concrete type. On its own such a trait is wired like any other component, but choosing the concrete type would otherwise mean writing a provider impl by hand for every type you want to plug in. `#[cgp_type]` removes that friction. - -The macro's value is the additional generated constructs layered on top of the component expansion. Because every abstract-type provider follows the same trivial shape — "the associated type *is* this concrete type" — `#[cgp_type]` can generate that shape once and for all as a [`UseType`](../providers/use_type.md) blanket impl. A context then names the concrete type directly in its wiring (`UseType`) instead of defining a bespoke provider. This is the same convenience relationship that `#[cgp_getter]` has to `UseField`: a general-purpose provider parameterized by the thing the context wants to supply. - -Direct implementation remains available and is often the clearest choice. An abstract type can always be implemented straight on a concrete context through its consumer trait, which is barely more verbose than wiring `UseType` and is the most transparent way to show that a CGP abstract type is nothing more than a vanilla Rust trait with an associated type. - -## Syntax - -The macro is applied to a trait that contains exactly one associated type and no methods. The associated type may carry bounds, but it must not be generic or have a `where` clause. The simplest form takes no argument: - -```rust -#[cgp_type] -pub trait HasScalarType { - type Scalar; -} -``` - -Like `#[cgp_component]`, the component needs a provider trait name, and `#[cgp_type]` derives one from the associated type's name by default. The default provider name is the associated type name with a `TypeProvider` suffix, so `Scalar` yields the provider `ScalarTypeProvider` and the component name `ScalarTypeProviderComponent`. Note that the default is keyed off the *associated type* name, not the trait name. You can override it by passing a provider name, as with `#[cgp_component]`: - -```rust -#[cgp_type(ProvideScalar)] -pub trait HasScalarType { - type Scalar; -} -``` - -A bound on the associated type is preserved everywhere the type appears in the expansion. For example `type Scalar: Copy;` carries the `Copy` bound onto the generated provider trait and into the `where` clauses of the generated provider impls. - -## Syntax Grammar - -The attribute argument of `#[cgp_type]` is the same grammar as [`#[cgp_component]`](cgp_component.md)'s `CgpComponentArgs` — a bare provider name or the keyed `name`/`provider`/`context` form: - -```ebnf -CgpTypeArgs -> CgpComponentArgs // see #[cgp_component] -``` - -The only difference from `#[cgp_component]` is the default applied when `provider` is omitted: instead of failing, the macro derives the provider name from the *associated type's* name with a `TypeProvider` suffix (so `type Scalar;` yields `ScalarTypeProvider`). All other keys and their defaults behave exactly as documented for `#[cgp_component]`. - -## Expansion - -`#[cgp_type]` expands to the full `#[cgp_component]` output for the trait, followed by two abstract-type provider impls. The component part is exactly what `#[cgp_component(ScalarTypeProvider)]` would produce for an associated-type trait — the consumer trait, the provider trait, the consumer and provider blanket impls, the `ScalarTypeProviderComponent` marker, and the standard `UseContext` and `RedirectLookup` provider impls. The difference from a behavioral component is that every blanket impl forwards the *associated type* rather than a method; see [`#[cgp_component]`](cgp_component.md) for that core shape. - -The first extra construct is the [`UseType`](../providers/use_type.md) blanket impl, which is the heart of `#[cgp_type]`. It implements the provider trait for `UseType` by setting the abstract associated type to the generic parameter `Scalar`. Starting from: - -```rust -#[cgp_type] -pub trait HasScalarType { - type Scalar; -} -``` - -the macro generates: - -```rust -impl ScalarTypeProvider<__Context__> for UseType { - type Scalar = Scalar; -} -``` - -This says that `UseType` is a provider that supplies `T` as the abstract type. Wiring a context's `ScalarTypeProviderComponent` to `UseType` therefore implements `HasScalarType` for that context with `Scalar = f64`, with no bespoke provider needed. If the associated type carries a bound, that bound is copied into the impl's `where` clause so the concrete type must satisfy it. - -The second extra construct is a `WithProvider` impl, which adapts the foundational [`HasType`/`TypeProvider`](../components/has_type.md) machinery into this component. It implements the provider trait for `WithProvider<__Provider__>` whenever `__Provider__` is a `TypeProvider` for the component: - -```rust -impl<__Provider__, Scalar, __Context__> ScalarTypeProvider<__Context__> - for WithProvider<__Provider__> -where - __Provider__: TypeProvider<__Context__, ScalarTypeProviderComponent, Type = Scalar>, -{ - type Scalar = Scalar; -} -``` - -The `HasType`/`TypeProvider` relationship this builds on is CGP's single built-in abstract-type component: `HasType` is the consumer trait, `TypeProvider` is its provider trait, and `UseType` is itself a `TypeProvider` (`impl TypeProvider for UseType { type Type = Type; }`). The `WithProvider` impl lets a `#[cgp_type]` component be backed by a generic `TypeProvider`, so the same `UseType` value satisfies both the built-in `HasType` and any user-defined `#[cgp_type]` component. - -As with the other macros, each generated provider impl is paired with a matching `IsProviderFor` impl carrying the same bounds, and the desugarings above are the exact shape the macro emits today. - -## Examples - -A complete use defines the abstract type, wires a concrete type through `UseType`, and consumes `Self::Scalar` in generic code: - -```rust -use cgp::prelude::*; - -#[cgp_type] -pub trait HasScalarType { - type Scalar: Copy; -} - -pub struct App; - -delegate_components! { - App { - ScalarTypeProviderComponent: UseType, - } -} - -fn zero() -> Context::Scalar -where - Context: HasScalarType, - Context::Scalar: Default, -{ - Default::default() -} -``` - -`App` wires `ScalarTypeProviderComponent` to `UseType`, so the generated `UseType` blanket impl makes `App` implement `HasScalarType` with `Scalar = f64`. The `Copy` bound on the associated type is enforced on `f64` at the wiring site. - -The abstract type can equally be implemented directly on a concrete context, bypassing both `delegate_components!` and `UseType`: - -```rust -impl HasScalarType for App { - type Scalar = f64; -} -``` - -This direct form is only marginally longer than the wired form and is the most approachable for readers new to CGP, since it makes plain that an abstract-type component is an ordinary trait with an associated type. - -## Related constructs - -`#[cgp_type]` is the abstract-type specialization of [`#[cgp_component]`](cgp_component.md), inheriting its full expansion and provider-name override syntax while keying the default name off the associated type. Its central generated construct is the [`UseType`](../providers/use_type.md) provider, the type-level analogue of the [`UseField`](../providers/use_field.md) provider that [`#[cgp_getter]`](cgp_getter.md) generates. It builds on CGP's foundational [`HasType`/`TypeProvider`](../components/has_type.md) component via the generated `WithProvider` impl. Abstract-type components are wired with [`delegate_components!`](delegate_components.md) and checked with [`check_components!`](check_components.md), and they are imported into other definitions with [`#[use_type]`](../attributes/use_type.md). When an abstract type's only role is to be a getter's return type, [`#[cgp_auto_getter]`](cgp_auto_getter.md) can declare it inline instead. - -## Source - -- Entry point: `cgp_type` in [crates/macros/cgp-macro-lib/src/cgp_type.rs](../../../crates/macros/cgp-macro-lib/src/cgp_type.rs), which extracts the single associated type, derives the default `{Type}TypeProvider` provider name from the associated type's identifier, runs the `#[cgp_component]` `preprocess → eval` pipeline, and converts the result into `ItemCgpType`. -- Logic: [crates/macros/cgp-macro-core/src/types/cgp_type/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_type/item.rs), which validates the trait shape (`extract_item_type_from_trait`) and builds the `UseType` and `WithProvider` provider impls. -- Runtime `HasType`, `TypeProvider`, and `UseType` definitions: [crates/core/cgp-type/src/](../../../crates/core/cgp-type/src/) (`traits/has_type.rs` and `impls/use_type.rs`). -- Internal walkthrough (the pipeline stages, the function that synthesizes each generated item, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/cgp_type.md](../../implementation/entrypoints/cgp_type.md). diff --git a/docs/reference/macros/check_components.md b/docs/reference/macros/check_components.md deleted file mode 100644 index bebb6e86..00000000 --- a/docs/reference/macros/check_components.md +++ /dev/null @@ -1,218 +0,0 @@ -# `check_components!` - -`check_components!` asserts at compile time that a context's wiring is complete, generating `CanUseComponent`-based checks that force the compiler to report exactly which dependency is missing. - -## Purpose - -`check_components!` exists because CGP wiring is lazy. When [`delegate_components!`](delegate_components.md) records that a context delegates a component to some provider, the type system does not eagerly verify that the provider can actually satisfy that component for that context with all of its transitive dependencies met. The [`DelegateComponent`](../traits/delegate_component.md) impl is accepted on its own terms; whether the provider's `where` bounds hold is only tested when something downstream tries to *use* the component. A context can therefore look fully wired and still fail the moment a consumer trait is invoked. - -When that failure happens far from the wiring, the error is hard to read. Asking only "does this context implement the consumer trait?" makes the compiler report the outermost unmet bound — typically that the provider does not implement the provider trait — without explaining why, because Rust hides the indirect reasoning behind that one conclusion. The root cause, often a single missing getter or type, is buried. - -`check_components!` solves this by turning the question into a `CanUseComponent` check. [`CanUseComponent`](../traits/can_use_component.md) is satisfied only when the context both delegates the component and the delegated provider satisfies [`IsProviderFor`](../traits/is_provider_for.md) for that context. Because `IsProviderFor` carries the provider's real `where` bounds, routing the check through it forces the compiler to evaluate and report those bounds, so an unsatisfied transitive requirement surfaces as a detailed error pointing at the actual missing dependency. The macro writes these checks for you, as a compile-time-only test: a successful build *is* the passing assertion. - -## Syntax - -The macro takes one or more check tables, each a context type followed by a brace-delimited list of the components to check on it. The simplest table lists bare component names: - -```rust -check_components! { - Person { - GreeterComponent, - } -} -``` - -Each entry names a component the macro should confirm `Person` can use. Multiple tables may appear in a single invocation, each beginning with its own context type and optional attributes. - -For a component with generic parameters, the parameters to check are given after a colon. A single parameter is written bare; multiple parameters are grouped into a tuple, mirroring how the provider trait groups them in its `IsProviderFor` `Params` position: - -```rust -check_components! { - MyApp { - AreaOfShapeCalculatorComponent: Rectangle, // one parameter - TransformCalculatorComponent: (Rectangle, f64), // two parameters, as a tuple - } -} -``` - -Array syntax on either side expands to the cartesian product of the bracketed entries, so a set of components can be checked against a set of parameters in one line. A bracketed value checks one component against several parameter sets; a bracketed key checks several components against one parameter set; bracketing both checks every combination: - -```rust -check_components! { - MyApp { - [AreaCalculatorComponent, RotatorComponent]: [Rectangle, Circle], - } -} -``` - -The check trait's name can be set with `#[check_trait(Name)]` on the table. The macro otherwise derives a name of the form `__Check{Context}` (for example `__CheckPerson`) from the final segment of the context type's path, so a path-qualified context such as `some_mod::Person` also yields `__CheckPerson`; the override is needed when two `check_components!` tables in the same module would otherwise collide. A leading `<...>` generic list and a trailing `where` clause may also be attached to a table to introduce and constrain generics used by the checked parameters. - -A `#[check_providers(...)]` attribute changes what is checked: instead of verifying the context, it verifies that each listed provider is a provider for the context. This is the form to reach for when a higher-order provider needs each layer checked separately, since each provider in the list is asserted independently. It must list at least one provider and may appear at most once on a table; an empty list or a repeated attribute is a compile error. - -## Syntax Grammar - -The input to `check_components!` is one or more check tables, each an optional attribute set and generic list, a context type, an optional `where` clause, and a brace-delimited list of check entries: - -```ebnf -CheckComponents -> CheckTable+ - -CheckTable -> TableAttr* Generics? ContextType WhereClause? `{` CheckEntries `}` - -TableAttr -> `#` `[` `check_trait` `(` IDENTIFIER `)` `]` - | `#` `[` `check_providers` `(` Type ( `,` Type )* `,`? `)` `]` - -ContextType -> Type - -CheckEntries -> ( CheckEntry ( `,` CheckEntry )* `,`? )? - -CheckEntry -> CheckKey ( `:` CheckValue )? - -CheckKey -> Type - | `[` Type ( `,` Type )* `,`? `]` - -CheckValue -> CheckParam - | `[` CheckParam ( `,` CheckParam )* `,`? `]` - -CheckParam -> Generics? Type -``` - -A single invocation may carry several `CheckTable`s, each with its own context type. The optional `#[check_trait(...)]` overrides the derived `__Check{Context}` trait name, and `#[check_providers(...)]` switches the check to verify the listed providers instead of the context. The `where` clause and a leading `Generics` list introduce and constrain generics used by the checked parameters. A `CheckEntry`'s value is omitted for a component with no generic parameters; when present, a bracketed `CheckKey` or `CheckValue` expands to the cartesian product, so a set of components is checked against a set of parameters. `WhereClause`, `Generics`, and `Type` are Rust grammar productions. - -## Expansion - -A check table expands to one marker trait plus one impl per checked entry. The marker trait is an alias whose supertrait is the check being asserted; each impl is an empty body that compiles only if that supertrait holds for the entry. Starting from: - -```rust -check_components! { - Person { - GreeterComponent, - } -} -``` - -the macro emits a check trait carrying `CanUseComponent` as its supertrait, followed by an impl of that trait for `Person` at the listed component and a unit params tuple: - -```rust -trait __CheckPerson<__Component__, __Params__: ?Sized>: - CanUseComponent<__Component__, __Params__> -{} - -impl __CheckPerson for Person {} -``` - -The impl compiles only if `Person: CanUseComponent`, which in turn requires that `Person` delegates `GreeterComponent` and that its delegate satisfies `IsProviderFor`. If the provider's dependencies are unmet — say it needs a `name` field the context lacks — the compiler reports the unsatisfied `HasField` bound rather than a bare "provider trait not implemented", which is the whole point of the indirection through `CanUseComponent`. The check trait name follows the `__Check{Context}` pattern, and the generic parameters are literally `__Component__` and `__Params__` in the emitted code. - -Generic parameters appear in the `__Params__` slot of each impl. A single parameter is placed there directly, and multiple parameters as a tuple: - -```rust -// AreaOfShapeCalculatorComponent: Rectangle -impl __CheckMyApp for MyApp {} - -// TransformCalculatorComponent: (Rectangle, f64) -impl __CheckMyApp for MyApp {} -``` - -Array syntax expands to the cartesian product before emitting impls, so `[AreaCalculatorComponent, RotatorComponent]: [Rectangle, Circle]` produces four impls — each component paired with each parameter — and the table-level generics and `where` clause are merged into every impl. A `<'a, I> Context where I: Clone { FooComponent: &'a I }` table, for instance, expands to `impl<'a, I> __CheckContext for Context where I: Clone {}`. - -The `#[check_providers(...)]` form changes both the supertrait and the implementing type. The check trait gains `IsProviderFor` as its supertrait instead of `CanUseComponent`, and the impls are written for each listed provider rather than for the context: - -```rust -check_components! { - #[check_trait(CheckScaledRectangleProviders)] - #[check_providers( - RectangleAreaCalculator, - ScaledAreaCalculator, - )] - ScaledRectangle { - AreaCalculatorComponent, - } -} -``` - -expands to: - -```rust -trait CheckScaledRectangleProviders<__Component__, __Params__: ?Sized>: - IsProviderFor<__Component__, ScaledRectangle, __Params__> -{} - -impl CheckScaledRectangleProviders for RectangleAreaCalculator {} -impl CheckScaledRectangleProviders - for ScaledAreaCalculator {} -``` - -Because each provider is checked on its own impl, a missing dependency affecting only the outer `ScaledAreaCalculator` produces an error on that line alone, while a dependency affecting the inner `RectangleAreaCalculator` errors on both — letting the failures localize the root cause. - -## Examples - -A check that catches a wiring mistake makes the value concrete. Given a greeter that depends on a `name` field, wired onto a context that has the wrong field name: - -```rust -use cgp::prelude::*; - -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasName, -{ - fn greet(&self) { - println!("Hello, {}!", self.name()); - } -} - -#[derive(HasField)] -pub struct Person { - pub first_name: String, // mismatch: GreetHello needs `name` -} - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} - -check_components! { - Person { - GreeterComponent, - } -} -``` - -The `delegate_components!` block compiles on its own because wiring is lazy, but `check_components!` forces the assertion `Person: CanUseComponent`, which fails to compile and reports that `Person` is missing the `name` field — pinpointing the mismatch at the wiring site instead of at some distant call to `person.greet()`. - -A generic-parameter check supplies the parameters explicitly: - -```rust -#[cgp_component(AreaOfShapeCalculator)] -pub trait CanCalculateAreaOfShape { - fn area(&self, shape: &Shape) -> f64; -} - -check_components! { - MyApp { - AreaOfShapeCalculatorComponent: [Rectangle, Circle], - } -} -``` - -This verifies `MyApp: CanCalculateAreaOfShape` and `MyApp: CanCalculateAreaOfShape` in one table. - -## Related constructs - -`check_components!` verifies the wiring produced by [`delegate_components!`](delegate_components.md) for components defined with [`#[cgp_component]`](cgp_component.md). Its checks are built on [`CanUseComponent`](../traits/can_use_component.md), which itself relies on [`DelegateComponent`](../traits/delegate_component.md) and [`IsProviderFor`](../traits/is_provider_for.md); the `#[check_providers(...)]` form checks `IsProviderFor` on named providers directly, which is especially useful for higher-order providers wired through [`use_delegate.md`](../providers/use_delegate.md). A standalone `check_components!` block paired with `delegate_components!` is the form advanced codebases rely on, because it can check what the fused derivation cannot — generic keys, opened or namespaced wiring, and per-provider layers. For basic wiring, and while getting started with CGP, [`delegate_and_check_components!`](delegate_and_check_components.md) fuses the delegation and its check into one step so a newcomer cannot forget to check; note its default check trait name (`__CanUse{Context}`) differs from this macro's (`__Check{Context}`) so the two can coexist in one module. - -## Source - -- Entry point: `check_components` in [crates/macros/cgp-macro-lib/src/check_components.rs](../../../crates/macros/cgp-macro-lib/src/check_components.rs), which parses `CheckComponentsTables` and emits their items. -- Logic: [crates/macros/cgp-macro-core/src/types/check_components/](../../../crates/macros/cgp-macro-core/src/types/check_components/) — table parsing, the `#[check_trait]`/`#[check_providers]` attributes, the `__Check{Context}` name derivation, and the choice between `CanUseComponent` and `IsProviderFor` supertraits are all in `table.rs`; key and value parsing (including array syntax) in `key.rs` and `value.rs`; and the cartesian-product expansion of entries in `entry.rs`. -- Internal walkthrough (the pipeline, the AST types behind each grammar form, the corner-case handling, and the index of expansion snapshots): [implementation/entrypoints/check_components.md](../../implementation/entrypoints/check_components.md). diff --git a/docs/reference/macros/delegate_and_check_components.md b/docs/reference/macros/delegate_and_check_components.md deleted file mode 100644 index 432d6ba3..00000000 --- a/docs/reference/macros/delegate_and_check_components.md +++ /dev/null @@ -1,190 +0,0 @@ -# `delegate_and_check_components!` - -`delegate_and_check_components!` wires a context's components and asserts the wiring is complete in one step, combining [`delegate_components!`](delegate_components.md) with [`check_components!`](check_components.md). - -## Purpose - -`delegate_and_check_components!` exists so that a context's wiring is checked the moment it is written, with no separate test block to remember. Because CGP wiring is lazy — a [`delegate_components!`](delegate_components.md) entry is accepted without verifying that the chosen provider can actually satisfy the component — it is easy to leave a context that compiles but fails at the first call to a consumer trait. A standalone [`check_components!`](check_components.md) block closes that gap, but keeping it in sync with the wiring is manual: add a delegation and you must remember to add its check. This macro removes that bookkeeping by deriving the checks directly from the delegations. - -This macro is aimed at basic wiring and at getting started with CGP, rather than at advanced code. Its value is that a newcomer following a tutorial cannot forget to write a separate check and then be tripped by the confusing errors lazy wiring produces at the first use of a component. The derivation it performs only understands the plain `Component: Provider` delegation form, so it cannot easily generate check traits for the advanced mappings — generic-parameter dispatch through the `open` statement and `@`-path keys, [namespace](cgp_namespace.md) joins, and per-layer higher-order checks — which need concrete parameters or providers the fused derivation cannot infer from the delegation alone. Larger and more advanced codebases therefore keep [`delegate_components!`](delegate_components.md) and [`check_components!`](check_components.md) separate, using the standalone check block for the control the per-entry derivation cannot give: `#[check_providers(...)]`, concrete parameters for generic keys, and checks over opened or namespaced wiring. One case makes this macro not merely unnecessary but *incorrect*: an [aggregate provider](../../concepts/aggregate-providers.md) — a `new SomeComponents { … }` table (see [`delegate_components!`](delegate_components.md)) that defines a zero-sized provider dispatching each component to a sub-provider, which other contexts then delegate to as a reusable bundle. An aggregate provider is delegated *to* by contexts and is never its own context; it has no fields and never implements a provider trait with itself in the context position, so the derived `CanUseComponent` assertion — which asks whether the *target* can use the component as a context — cannot hold, and `delegate_and_check_components!` would report spurious failures. Wire an aggregate provider with plain `delegate_components!`; it is verified indirectly when a real context that delegates to it is checked, or directly with a [`check_components!`](check_components.md) `#[check_providers(...)]` block that asserts [`IsProviderFor`](../traits/is_provider_for.md) on it for a real context. The invariant across both approaches is that a context's wiring is checked somehow; this macro is the beginner-level way to guarantee that for simple contexts, and the two separate macros are the way that scales. - -Functionally, the macro emits exactly what writing both macros by hand would, with the check entries inferred from the delegation keys. Every delegated component is checked unless explicitly opted out, so the default behavior is "wire it and prove it works." - -## Syntax - -The macro takes the same table shape as [`delegate_components!`](delegate_components.md) — an optional `new` keyword and generics, a target type, and brace-delimited `Key: Value` delegation entries — and additionally accepts a few attributes that control the checking half. The basic form simply wires and checks each entry: - -```rust -delegate_and_check_components! { - ScaledRectangle { - AreaCalculatorComponent: - ScaledAreaCalculator, - } -} -``` - -The check trait's name defaults to `__CanUse{Context}` (for example `__CanUseScaledRectangle`). This deliberately differs from the `__Check{Context}` name that [`check_components!`](check_components.md) derives, so that both macros can be used once each in the same module without a name clash. A table-level `#[check_trait(Name)]` attribute overrides the derived name: - -```rust -delegate_and_check_components! { - #[check_trait(TestScaledRectangle)] - ScaledRectangle { - AreaCalculatorComponent: - ScaledAreaCalculator, - } -} -``` - -A component with generic parameters needs a `#[check_params(...)]` attribute on its entry, because the derived check would otherwise have no parameters to test. The delegation half does not need the parameters — the `DelegateComponent` impl is generic over them — but the check half does, so `#[check_params(...)]` supplies them, with the same single-versus-tuple convention as [`check_components!`](check_components.md): - -```rust -delegate_and_check_components! { - MyApp { - #[check_params( - Rectangle, - Circle, - )] - AreaOfShapeCalculatorComponent: - UseDelegate, - } -} -``` - -A `#[skip_check]` attribute on an entry wires it without generating any check, for cases where that component is verified separately. This avoids having to split out a second plain `delegate_components!` block just to wire one component without a check: - -```rust -delegate_and_check_components! { - ScaledRectangle { - #[skip_check] - AreaCalculatorComponent: - ScaledAreaCalculator, - } -} -``` - -The `#[check_params(...)]` and `#[skip_check]` attributes are mutually exclusive on a given key, and at most one may appear. The same array syntax that `delegate_components!` allows on the key side works here, with check params attaching per bracketed key as needed. - -## Syntax Grammar - -The body of `delegate_and_check_components!` is the same table shape as [`delegate_components!`](delegate_components.md), with an optional table-level check-trait attribute and a per-entry check attribute added: - -```ebnf -DelegateAndCheck -> TableAttr* Generics? `new`? TargetType `{` TableBody `}` - -TableAttr -> `#` `[` `check_trait` `(` IDENTIFIER `)` `]` - -TableBody -> Statement* ( CheckedMapping ( `,` CheckedMapping )* `,`? )? - -CheckedMapping -> EntryAttr? Mapping // Mapping, Key, ProviderValue — see delegate_components! - -EntryAttr -> `#` `[` `check_params` `(` Type ( `,` Type )* `,`? `)` `]` - | `#` `[` `skip_check` `]` -``` - -The `Mapping`, `Key`, `ProviderValue`, and `Statement` productions are exactly those of [`delegate_components!`](delegate_components.md); only the attributes differ. The table-level `#[check_trait(...)]` overrides the derived `__CanUse{Context}` trait name. Each mapping may carry at most one `EntryAttr`, and `#[check_params(...)]` and `#[skip_check]` are mutually exclusive: `#[check_params(...)]` supplies the generic parameters the derived check needs for a component with type parameters, and `#[skip_check]` wires the entry without generating a check at all. - -## Expansion - -The macro emits the delegation impls exactly as [`delegate_components!`](delegate_components.md) would, then appends a check trait and one impl per non-skipped entry, exactly as [`check_components!`](check_components.md) would. Starting from: - -```rust -delegate_and_check_components! { - #[check_trait(CheckMyContext)] - MyContext { - NameTypeProviderComponent: UseType, - NameGetterComponent: UseField, - } -} -``` - -the macro first produces the wiring half — a [`DelegateComponent`](../traits/delegate_component.md) impl and an [`IsProviderFor`](../traits/is_provider_for.md) forwarding impl for each entry: - -```rust -impl DelegateComponent for MyContext { - type Delegate = UseType; -} -impl<__Context__, __Params__> - IsProviderFor for MyContext -where - UseType: IsProviderFor, -{} - -impl DelegateComponent for MyContext { - type Delegate = UseField; -} -impl<__Context__, __Params__> - IsProviderFor for MyContext -where - UseField: IsProviderFor, -{} -``` - -then the checking half — a check trait aliasing [`CanUseComponent`](../traits/can_use_component.md), with one impl per delegated component: - -```rust -trait CheckMyContext<__Component__, __Params__: ?Sized>: - CanUseComponent<__Component__, __Params__> -{} - -impl CheckMyContext for MyContext {} -impl CheckMyContext for MyContext {} -``` - -Without the `#[check_trait(...)]` override, the trait would instead be named `__CanUseMyContext`. The whole output is identical to writing a `delegate_components!` block followed by a `check_components!` block whose check trait carries the `__CanUse{Context}` name. - -A `#[check_params(...)]` entry expands its parameters into the `__Params__` slot of the generated check impls, one impl per listed parameter, while the delegation impl stays generic over the parameter. The earlier `MyApp` table therefore checks `MyApp` at `Rectangle` and at `Circle` (`impl __CanUseMyApp for MyApp {}` and likewise for `Circle`), even though its single `DelegateComponent` impl is parameter-generic. A `#[skip_check]` entry contributes its delegation impls but no check impl, so it appears in the wiring half and is absent from the checking half. - -A generic table threads its generics through both halves. ` MyContext { ... }` yields `impl DelegateComponent<...> for MyContext` delegations alongside `impl __CanUseMyContext<..., ()> for MyContext {}` checks. - -A delegation key may also carry its own generic parameters, and they are bound on the derived check impl. An entry such as ` BarGetterAtComponent: UseField` — the array-key generic form [`delegate_components!`](delegate_components.md) accepts — checks as `impl __CanUse…, ()> for MyContext {}`, and a `#[check_params((I, Index<0>))]` value that mentions the key generic is bound the same way. This lets a component whose marker is generic be wired and checked in one step without splitting the check into a separate block. - -## Examples - -A main context wired and checked together is the intended use: - -```rust -use cgp::prelude::*; - -#[derive(HasField)] -pub struct MyContext { - pub name: String, -} - -delegate_and_check_components! { - MyContext { - NameTypeProviderComponent: UseType, - NameGetterComponent: UseField, - } -} -``` - -If `MyContext` were missing the `name` field, the derived check on `NameGetterComponent` would fail to compile and report the missing `HasField` bound, rather than letting the gap slip through to a later use of `name()`. - -Mixing checked and skipped entries lets a higher-order delegation be verified elsewhere while the rest is checked inline: - -```rust -delegate_and_check_components! { - ScaledRectangle { - AreaCalculatorComponent: - ScaledAreaCalculator, - - #[skip_check] - TransformCalculatorComponent: - ComplexTransform, // checked in a dedicated check_components! block - } -} -``` - -## Related constructs - -`delegate_and_check_components!` is the fusion of [`delegate_components!`](delegate_components.md) and [`check_components!`](check_components.md): the wiring half behaves exactly like the former and the checking half like the latter, so the semantics of [`DelegateComponent`](../traits/delegate_component.md), [`IsProviderFor`](../traits/is_provider_for.md), and [`CanUseComponent`](../traits/can_use_component.md) all carry over unchanged. It wires components defined with [`#[cgp_component]`](cgp_component.md) to providers written with [`#[cgp_impl]`](cgp_impl.md), [`#[cgp_provider]`](cgp_provider.md), or [`#[cgp_fn]`](cgp_fn.md), and supports nested-table values via [`use_delegate.md`](../providers/use_delegate.md) and field getters via [`use_field.md`](../providers/use_field.md). Reach for plain [`delegate_components!`](delegate_components.md) instead when building an [aggregate provider](../../concepts/aggregate-providers.md), and for a standalone [`check_components!`](check_components.md) block when a check needs `#[check_providers(...)]` or other control beyond per-entry `#[check_params]`/`#[skip_check]`. - -## Source - -- Entry point: `delegate_and_check_components` in [crates/macros/cgp-macro-lib/src/delegate_and_check_components.rs](../../../crates/macros/cgp-macro-lib/src/delegate_and_check_components.rs), which parses the table, evaluates the delegation half via the shared `DelegateTable`, derives a `CheckComponentsTable` from the keys, and emits both. -- Logic: [crates/macros/cgp-macro-core/src/types/delegate_and_check_components/](../../../crates/macros/cgp-macro-core/src/types/delegate_and_check_components/) — the `__CanUse{Context}` default name and `#[check_trait]` handling in `item.rs`, the `#[check_params]`/`#[skip_check]` parsing and their mutual exclusion in `check_params.rs`, the per-key conversion to check entries in `key_with_check_params.rs`, and the walk over delegation entries in `to_keys_with_check_params.rs`. -- Reused tables: the `DelegateTable` from [delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/) and the `CheckComponentsTable` from [check_components/](../../../crates/macros/cgp-macro-core/src/types/check_components/). -- Internal walkthrough (the two reused pipelines, the key-to-check derivation, the corner-case handling, and the index of expansion snapshots): [implementation/entrypoints/delegate_and_check_components.md](../../implementation/entrypoints/delegate_and_check_components.md). diff --git a/docs/reference/macros/delegate_components.md b/docs/reference/macros/delegate_components.md deleted file mode 100644 index 5479b4c4..00000000 --- a/docs/reference/macros/delegate_components.md +++ /dev/null @@ -1,259 +0,0 @@ -# `delegate_components!` - -`delegate_components!` builds a context's type-level wiring table, implementing `DelegateComponent` so that each component name maps to the provider that should implement it. - -## Purpose - -`delegate_components!` is how a concrete context chooses which provider implements each of its components. A CGP component (created by [`#[cgp_component]`](cgp_component.md)) separates the consumer trait callers use from the provider trait implementers write, but that separation leaves a question open: for a given context, *which* provider supplies the behavior? `delegate_components!` answers it by recording one entry per component — the component name as the key, the chosen provider as the value — on the context type. - -The mental model is a type-level table, analogous to an object's method table (vtable) in object-oriented languages. Where a vtable maps method names to function pointers resolved at runtime, this table maps component-name types to provider types resolved at compile time. Looking a component up in the table yields the provider, and the blanket impls generated by `#[cgp_component]` route the consumer-trait call through whatever provider the lookup returns. The macro's job is to populate that table without making the user hand-write the underlying [`DelegateComponent`](../traits/delegate_component.md) impls. - -The reason a macro is worth having is that each entry expands to more than a single impl. Besides the `DelegateComponent` impl that stores the entry, the macro emits an [`IsProviderFor`](../traits/is_provider_for.md) impl that forwards the chosen provider's dependencies back through the table, so that a missing transitive requirement still surfaces as a usable compiler error rather than a dead end. Writing both impls by hand for every component, on every context, would be tedious and error-prone; `delegate_components!` generates them from a compact table syntax. - -## Syntax - -The macro takes a target type followed by a brace-delimited list of `Key: Value` entries. The target is the context (or an [aggregate provider](../../concepts/aggregate-providers.md)) whose table is being defined; each key is a component-name type and each value is the provider type to delegate that component to: - -```rust -delegate_components! { - Rectangle { - AreaCalculatorComponent: RectangleArea, - } -} -``` - -An optional `new` keyword in front of the target type makes the macro define the target struct as well, saving a separate declaration. `new MyComponents { ... }` emits `struct MyComponents;` (or a generic struct, if the target carries type parameters) in addition to the table impls. This is the idiomatic way to declare an **[aggregate provider](../../concepts/aggregate-providers.md)** — a zero-sized provider whose only purpose is to hold a table that dispatches each component to a sub-provider, so other contexts can delegate a whole group of components to it as one reusable unit. An aggregate provider is a *provider*, not a context: it forwards each component's provider trait through its `DelegateComponent` table and is delegated to by real contexts, but it never implements a provider trait with itself in the context position. Consequently it must be wired with plain `delegate_components!`, never [`delegate_and_check_components!`](delegate_and_check_components.md), whose derived `CanUseComponent` check assumes the target is a usable context and would fail spuriously on a provider that is not one. - -When several components share the same provider, the array syntax on the key side lets one value serve multiple keys. A bracketed list of component names before the colon expands to one entry per name, all pointing at the same value: - -```rust -delegate_components! { - MyComponents { - [ - FooComponent, - BarComponent, - ]: FooBarProvider, - BazComponent: BazProvider, - } -} -``` - -A leading `<...>` generic list on the target makes the whole table generic, so the same wiring can apply across a family of context types — for example `delegate_components! { MyContext { ... } }` wires every `MyContext` at once. - -The recommended way to dispatch a component on its generic parameter is the `open` statement, which folds the per-value entries directly into the context's own table. A leading `open AreaCalculatorComponent;` header opens one or more components for per-key wiring, after which a `@`-path entry such as `@AreaCalculatorComponent.Rectangle: RectangleArea` assigns a provider to a single value of that component's dispatch parameter. The braces are optional when opening a single component, so `open AreaCalculatorComponent;` is equivalent to `open { AreaCalculatorComponent };`; the braced list is only required to open several components at once: - -```rust -delegate_components! { - MyApp { - open AreaCalculatorComponent; - - @AreaCalculatorComponent.Rectangle: RectangleArea, - @AreaCalculatorComponent.Circle: CircleArea, - } -} -``` - -This wires `MyApp` to calculate the area of a `Rectangle` through `RectangleArea` and a `Circle` through `CircleArea` without naming any separate table. Each `@Component.Key` key may use the array shorthand on its final segment, `@AreaCalculatorComponent.[Rectangle, Circle]: SomeProvider`, and may carry generic parameters, `@SomeComponent.<'a, T> &'a T: SomeProvider`. Under the hood `open` redirects the component's lookup along a [`@`-path](cgp_namespace.md) into the context's own table, resolved by the [`RedirectLookup`](../providers/redirect_lookup.md) impl that every [`#[cgp_component]`](cgp_component.md) generates — so `open`-based dispatch needs no [`#[derive_delegate]`](../attributes/derive_delegate.md) on the component. `open` is a lightweight form of the full [namespace](cgp_namespace.md) feature, suited to small applications and self-contained code where a context wires its own components directly; it does not combine with a joined namespace where the component carries a `#[prefix(...)]`, in which case the per-value entries must be written with the full prefixed path rather than through `open`. The full namespace feature is what lets wiring scale across a large code base with many components. - -> **Legacy:** A value may also itself open a nested table, the older shape for dispatching a generic-parameter component. Writing `UseDelegate` as a value both wires the outer key to [`UseDelegate`](../providers/use_delegate.md) over an inner table and defines that inner table in place: -> -> ```rust -> delegate_components! { -> MyApp { -> AreaCalculatorComponent: -> UseDelegate Rectangle: RectangleArea, -> Circle: CircleArea, -> }>, -> } -> } -> ``` -> -> This nested-table form, the [`UseDelegate`](../providers/use_delegate.md) provider, and the [`#[derive_delegate]`](../attributes/derive_delegate.md) attribute that generates it together form a legacy dispatch mechanism. The `open` statement above achieves the same per-type dispatch with better ergonomics — no separate inner-table type and no `UseDelegate` wrapper — and is preferred for new code. The nested-table form is retained for compatibility and is expected to be deprecated, and eventually removed, once the namespace-based `open` form is shown to cover every dispatch case. - -Beyond plain `Key: Value` entries and `open`, the table body also accepts the other namespace-oriented statement forms used to opt a context into a [`#[cgp_namespace]`](cgp_namespace.md): a leading `namespace SomeNamespace;` header that forwards every lookup through that namespace, `@`-path keys such as `@app.ErrorRaiserComponent` that target a route rather than a bare component name, and `for in SomeTable { ... }` loops that pull entries out of another lookup table. These forms are described under [`#[cgp_namespace]`](cgp_namespace.md), where they are most often written. - -The macro accepts no attributes on the table or on any of its keys — including keys nested inside a `UseDelegate` value — and rejects any it finds with a spanned "unsupported attribute" error rather than discarding it. Attribute-driven variants such as `#[check_params(...)]` and `#[skip_check]` belong to [`delegate_and_check_components!`](delegate_and_check_components.md), not here. - -## Syntax Grammar - -The body of `delegate_components!` is an optional generic list and `new` keyword, a target type, and a brace-delimited table of mappings: - -```ebnf -DelegateComponents -> Generics? `new`? TargetType `{` TableBody `}` - -TargetType -> Type - -TableBody -> Statement* ( Mapping ( `,` Mapping )* `,`? )? - -Statement -> OpenStmt | NamespaceStmt | ForStmt - -OpenStmt -> `open` ( `{` Type ( `,` Type )* `,`? `}` | Type ) `;` // NamespaceStmt, ForStmt — see #[cgp_namespace] - -Mapping -> Key `:` ProviderValue - | Key `->` ProviderValue - | Key `=>` Path - -Key -> SingleKey | MultiKey | PathKey -SingleKey -> Generics? Type -MultiKey -> `[` SingleKey ( `,` SingleKey )* `,`? `]` -PathKey -> Generics? Path - -ProviderValue -> Type - | IDENTIFIER `<` `new` TargetType `{` TableBody `}` `>` - -Path -> `@` PathSegment ( `.` PathSegment )* // see Path! -``` - -A leading `Generics` list (a Rust `< … >`) makes the whole table generic over the target; the `new` keyword additionally emits the target struct. Each `Mapping` chooses one of three operators: `` `:` `` maps a key directly to the named provider — the common form — while `` `->` `` delegates to the value's own entry for that key and `` `=>` `` redirects the lookup along an `@`-`Path`; the operators other than `:` are used mainly by the namespace machinery and detailed under [`#[cgp_namespace]`](cgp_namespace.md). A `Key` may be a single type, a bracketed list expanding to one entry per name, or an `@`-`PathKey`. The nested-table `ProviderValue` form wires the key to a `UseDelegate`-style wrapper while defining the inner table in place. `Mapping`, `Key`, and `ProviderValue` are the shared productions reused by [`delegate_and_check_components!`](delegate_and_check_components.md) and [`#[cgp_namespace]`](cgp_namespace.md). An `OpenStmt` opens each listed component for per-value wiring directly in the context's table, after which `@Component.Key` path keys populate it; its brace-delimited list may be written without braces when it opens exactly one component (`open Component;`), while opening several at once requires the braces. The `NamespaceStmt` and `ForStmt` statement forms and the `Path` segment rules are defined under [`#[cgp_namespace]`](cgp_namespace.md) and [`Path!`](path.md). The macro accepts no attributes on the table or its entries and rejects any it finds. - -## Expansion - -Each table entry expands to a pair of impls: a `DelegateComponent` impl that stores the value, and an `IsProviderFor` impl that forwards the value's dependencies. Starting from the single-entry table: - -```rust -delegate_components! { - Rectangle { - AreaCalculatorComponent: RectangleArea, - } -} -``` - -the macro emits, first, the `DelegateComponent` impl that records the entry — `AreaCalculatorComponent` as the key, `RectangleArea` as the `Delegate` value: - -```rust -impl DelegateComponent for Rectangle { - type Delegate = RectangleArea; -} -``` - -This impl alone is what the provider blanket impl from [`#[cgp_component]`](cgp_component.md) reads when it looks the component up: because `Rectangle` delegates `AreaCalculatorComponent` to `RectangleArea`, `Rectangle` inherits `AreaCalculator` from `RectangleArea`, and from there the consumer blanket impl gives `Rectangle` the `CanCalculateArea` consumer trait. The lookup is the whole mechanism; the explicit, hand-written equivalent of one entry is exactly the `impl DelegateComponent<...>` block above. - -Second, the macro emits the matching `IsProviderFor` impl, which propagates the chosen provider's requirements. It is generic over a context and a params tuple, and holds whenever the delegated provider is itself a provider for that component: - -```rust -impl<__Context__, __Params__> - IsProviderFor for Rectangle -where - RectangleArea: IsProviderFor, -{} -``` - -This second impl is the reason missing dependencies stay diagnosable. `RectangleArea`'s own `IsProviderFor` impl (generated by [`#[cgp_provider]`](cgp_provider.md) or [`#[cgp_impl]`](cgp_impl.md)) carries the same `where` bounds it needs to be a provider, so an unsatisfied transitive requirement flows back through this forwarding impl to the point of use. The generic parameters are literally named `__Context__` and `__Params__` in the emitted code, not `Context`/`Params`. - -The array syntax simply repeats this pair per key. The table - -```rust -delegate_components! { - MyComponents { - [FooComponent, BarComponent]: FooBarProvider, - BazComponent: BazProvider, - } -} -``` - -expands as if each bracketed key had been written on its own line, yielding three `DelegateComponent` impls (`FooComponent → FooBarProvider`, `BarComponent → FooBarProvider`, `BazComponent → BazProvider`) and their three corresponding `IsProviderFor` impls. - -A nested-table value expands in two parts. The inner table is lifted out into its own `delegate_components!`-equivalent definition, and the outer entry is wired to `UseDelegate` over the inner table's type. The earlier `MyApp` example is equivalent to: - -```rust -delegate_components! { - MyApp { - AreaCalculatorComponent: UseDelegate, - } -} - -delegate_components! { - new AreaCalculatorComponents { - Rectangle: RectangleArea, - Circle: CircleArea, - } -} -``` - -Because the inner value was written with `new`, the macro also defines `struct AreaCalculatorComponents;`. At the impl level this means `MyApp`'s table maps `AreaCalculatorComponent` to `UseDelegate`, while `AreaCalculatorComponents` is a second table whose `Shape`-keyed entries (`Rectangle`, `Circle`) tell `UseDelegate` which provider to dispatch to for each shape. See [`use_delegate.md`](../providers/use_delegate.md) for how `UseDelegate` performs that inner lookup. This nested-table expansion is the legacy form; the `open` expansion below is the modern equivalent. - -The `open` statement expands each listed component to a redirect entry, with the per-value mappings stored directly on the context. From: - -```rust -delegate_components! { - MyApp { - open AreaCalculatorComponent; - - @AreaCalculatorComponent.Rectangle: RectangleArea, - @AreaCalculatorComponent.Circle: CircleArea, - } -} -``` - -the `open AreaCalculatorComponent;` header wires `AreaCalculatorComponent` to a `RedirectLookup` rooted at the component name inside `MyApp`'s own table: - -```rust -impl DelegateComponent for MyApp { - type Delegate = RedirectLookup>; -} -``` - -Each `@AreaCalculatorComponent.Rectangle: RectangleArea` entry then stores its provider in that same table under the path key, so `MyApp` gains `DelegateComponent>>` with `Delegate = RectangleArea`. The [`RedirectLookup`](../providers/redirect_lookup.md) impl that [`#[cgp_component]`](cgp_component.md) generates for `AreaCalculator` appends the dispatch parameter — here `Rectangle` — onto the redirect path and reads the result back, so `MyApp: CanCalculateArea` resolves to `RectangleArea`. The lookup keys on the same `Shape` parameter the legacy `UseDelegate` form keys on; the difference is only that the per-value entries live on the context itself rather than in a separate table type. - -## Examples - -A complete wiring shows the table feeding the consumer trait. Given a component and a provider for it: - -```rust -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} -``` - -a concrete context wires the component and then calls through it: - -```rust -#[derive(HasField)] -pub struct Rectangle { - pub width: f64, - pub height: f64, -} - -delegate_components! { - Rectangle { - AreaCalculatorComponent: RectangleArea, - } -} - -fn print_area(rect: &Rectangle) { - println!("area = {}", rect.area()); // resolves via Rectangle's table to RectangleArea -} -``` - -An [aggregate provider](../../concepts/aggregate-providers.md) uses `new` to define its own table type, which other contexts can then delegate to as a single unit: - -```rust -delegate_components! { - new GeometryComponents { - AreaCalculatorComponent: RectangleArea, - PerimeterCalculatorComponent: RectanglePerimeter, - } -} -``` - -## Related constructs - -`delegate_components!` is the wiring step for components defined by [`#[cgp_component]`](cgp_component.md), and the providers it names are written with [`#[cgp_impl]`](cgp_impl.md), [`#[cgp_provider]`](cgp_provider.md), or [`#[cgp_fn]`](cgp_fn.md). Each entry expands to a [`DelegateComponent`](../traits/delegate_component.md) impl plus an [`IsProviderFor`](../traits/is_provider_for.md) impl. The `open` statement dispatches a component on its generic parameter through the [`RedirectLookup`](../providers/redirect_lookup.md) impl every component generates, and is the preferred alternative to the legacy nested-table values that rely on [`UseDelegate`](../providers/use_delegate.md); field-backed getters are commonly wired to [`UseField`](../providers/use_field.md). To verify a table is complete, pair it with a standalone [`check_components!`](check_components.md) — the form advanced codebases use, since it can check generic keys, opened or namespaced wiring, and per-provider layers — or, for basic wiring and while getting started, use [`delegate_and_check_components!`](delegate_and_check_components.md) to wire and check in one step so the check cannot be forgotten. Plain `delegate_components!` with no check is reserved for [aggregate providers](../../concepts/aggregate-providers.md) — the `new`-keyword bundles that other contexts delegate to but that are not contexts in their own right — since the fused check cannot apply to a target that is a provider rather than a context. When a component is defined inside a namespace, see [`#[cgp_namespace]`](cgp_namespace.md). - -## Source - -- Entry point: `delegate_components` in [crates/macros/cgp-macro-lib/src/delegate_components.rs](../../../crates/macros/cgp-macro-lib/src/delegate_components.rs), which parses a `DelegateTable`, validates that no attributes are present, evaluates it, and emits the tokens. -- Logic: [crates/macros/cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/) — the top-level table and the `new` keyword in `table/main.rs`, key parsing (single, array/`Multi`, path) in `key/`, value parsing including the nested-table form in `value/`, the statement forms (`open`, `namespace`, `for`) in `statement/` — with the `open` statement and its `RedirectLookup` expansion in `statement/open.rs` — and the `DelegateComponent`/`IsProviderFor` impl construction in `mapping/eval.rs`. -- Attribute rejection: `validate_attributes.rs`. -- Internal walkthrough (the pipeline, the AST types behind each grammar form, the corner-case handling, and the index of tests and expansion snapshots): [implementation/entrypoints/delegate_components.md](../../implementation/entrypoints/delegate_components.md). diff --git a/docs/reference/macros/path.md b/docs/reference/macros/path.md deleted file mode 100644 index 04f34458..00000000 --- a/docs/reference/macros/path.md +++ /dev/null @@ -1,99 +0,0 @@ -# `Path!` - -`Path!(@a.B.c)` is the type macro that builds a type-level path — a `PathCons` list of segments naming a route through nested delegation tables — from a dotted, `@`-prefixed sequence of names. - -## Purpose - -`Path!` exists to give a readable surface syntax for the `PathCons` spine that CGP uses to address an entry behind layers of delegation. A path names a route, read left to right, where each segment narrows a lookup one step: through a namespace, through a prefix, down to a component key. Writing that route as a nested `PathCons<…, PathCons<…, Nil>>` by hand is unwieldy and obscures the intent, so `Path!` lets it be written the way it reads — a dotted name like `@app.error.ErrorRaiserComponent` — and folds it into the corresponding spine. - -The macro is the path-shaped sibling of the other type-level construction macros. Where [`Symbol!`](symbol.md) turns a string literal into a single type-level string and [`Product!`](product.md)/`Sum!` build product and sum lists, `Path!` builds the routing list, sharing their right-nested, `Nil`-terminated shape. It is the construction half of the [`PathCons`](../types/path_cons.md) type, and the same `@`-path syntax it accepts is embedded directly inside [`#[cgp_namespace]`](cgp_namespace.md) entries and `#[prefix(...)]` attributes, which is where paths are most often written. - -## Syntax - -`Path!` takes a single `@`-prefixed path made of one or more segments separated by dots. The leading `@` is required — it is the sigil that marks the body as a path rather than a plain type — and at least one segment must follow it: - -```rust -Path!(@app) -Path!(@app.error) -Path!(@app.error.ErrorRaiserComponent) -``` - -Each segment is parsed as a type, and its first character decides how it is encoded. A segment that is a single identifier beginning with a lowercase letter — and is not a primitive type name — becomes a [`Symbol`](../types/chars.md) type-level string of that identifier; every other segment is kept as the named type it spells. So lowercase segments such as `app` and `error` become symbols, while capitalized segments such as `ErrorRaiserComponent` become references to those types (typically component keys or namespace markers). The exception for primitives means a lowercase name like `u32`, `bool`, `usize`, or `str` is treated as the named primitive type, not as a symbol. - -This is the same convention namespaces describe for their `@`-paths: dotted lowercase segments are field-name-style symbols and capitalized segments are named types. Mixing the two is normal — a path like `@my_app.ShowImplComponent` interleaves a symbol segment and a component segment. - -## Syntax Grammar - -The input to `Path!` is a leading `@` followed by one or more dot-separated segments: - -```ebnf -PathInput -> `@` PathSegment ( `.` PathSegment )* - -PathSegment -> Type -``` - -The leading `` `@` `` is required and at least one segment must follow. Each `PathSegment` is parsed as a Rust `Type`, but its encoding is decided semantically (see Expansion): a single lowercase identifier that is not a primitive type name becomes a `Symbol` type-level string, while every other segment — a capitalized name or a primitive — is kept as the named type. This same `@`-path grammar is what [`#[cgp_namespace]`](cgp_namespace.md) entries and `#[prefix(...)]` attributes embed, where it appears as the `Path` production. - -## Expansion - -`Path!` expands to a right-nested chain of [`PathCons`](../types/path_cons.md) terminated by `Nil`, with each segment encoded by the lowercase/capitalized rule. A three-segment path with one lowercase symbol and two named types desugars as follows: - -```rust -// before -Path!(@app.error.ErrorRaiserComponent) -``` - -```rust -// after — readable form -PathCons< - Symbol!("app"), - PathCons< - Symbol!("error"), - PathCons, - >, -> -``` - -The macro builds this by parsing the segments after the `@` into a list and folding them from right to left onto `Nil`, wrapping each segment in a `PathCons` whose tail is the accumulated rest. A single-segment path therefore becomes `PathCons`, and because a lowercase symbol segment is itself a `Symbol`/`Chars`/`Nil` chain, the fully desugared form of `@app` is `PathCons>>>, Nil>`. - -The same expansion appears verbatim inside the wirings that embed `@`-paths. A [`#[cgp_namespace]`](cgp_namespace.md) redirect entry `FooProviderComponent => @MyFooComponent` produces a `RedirectLookup<__Table__, PathCons>`, and a `#[prefix(@MyBarComponent in MyNamespace)]` attribute produces a `PathCons>` — the macro's fold is the same one driving those constructs. - -## Examples - -A path is typically written to express a redirect target. Used directly as a type, `Path!` names a route that a [`RedirectLookup`](../providers/redirect_lookup.md) can resolve against a table: - -```rust -use cgp::prelude::*; - -type ErrorRoute = Path!(@app.error.ErrorRaiserComponent); -// ErrorRoute = PathCons>> -``` - -In practice the same syntax is more often embedded in a namespace table than written through the bare macro, since [`#[cgp_namespace]`](cgp_namespace.md) accepts `@`-paths directly in its entries: - -```rust -use cgp::prelude::*; - -cgp_namespace! { - new MyNamespace { - FooProviderComponent => - @MyFooComponent, - } -} -// the entry's Delegate is RedirectLookup<__Table__, PathCons> -``` - -Either way the path is the same `PathCons` list; the macro and the namespace simply offer two places to write it. - -## Related constructs - -`Path!` constructs the [`PathCons`](../types/path_cons.md) spine, the type it desugars to, and its lowercase segments are [`Symbol!`](symbol.md) type-level strings. It is the routing-list counterpart to the product and sum construction macros [`Product!`](product.md) and [`Sum!`](sum.md), sharing their right-fold-onto-`Nil` shape. The paths it builds are consumed by [`RedirectLookup`](../providers/redirect_lookup.md) when resolving a delegation, and its `@`-path syntax is embedded throughout [`#[cgp_namespace]`](cgp_namespace.md), where namespace entries and `#[prefix(...)]` attributes use the same dotted form. - -## Source - -- Entry point: `Path` in [crates/macros/cgp-macro-lib/src/path.rs](../../../crates/macros/cgp-macro-lib/src/path.rs), which parses the body into a `UniPath` and emits its tokens. -- Parsing and codegen: [crates/macros/cgp-macro-core/src/types/path/](../../../crates/macros/cgp-macro-core/src/types/path/) — `unipath.rs` requires the leading `@`, parses the dot-separated segments, and right-folds them with `PathCons` onto `Nil`; `path_element.rs` decides per segment whether a lowercase, non-primitive identifier becomes a `Symbol` or the segment stays a named type. -- Runtime spine `PathCons`: [crates/core/cgp-base-types/src/types/path.rs](../../../crates/core/cgp-base-types/src/types/path.rs); the `RedirectLookup` provider that walks a path is in [crates/core/cgp-component/src/providers/redirect_lookup.rs](../../../crates/core/cgp-component/src/providers/redirect_lookup.rs). -- Internal walkthrough (the parse-and-emit pipeline, the per-segment symbol/type classification, the `PathCons` fold, and the index of tests): [implementation/entrypoints/path.md](../../implementation/entrypoints/path.md). diff --git a/docs/reference/macros/product.md b/docs/reference/macros/product.md deleted file mode 100644 index 945535c7..00000000 --- a/docs/reference/macros/product.md +++ /dev/null @@ -1,108 +0,0 @@ -# `Product!` and `product!` - -`Product![A, B, C]` is the type macro that builds a type-level list — a heterogeneous list of types encoded entirely in the type system — and the lowercase `product![a, b, c]` is its value-level counterpart that builds an actual value of that type. - -## Purpose - -`Product!` exists to represent an ordered sequence of types as a single type, so that a collection of fields can be reasoned about generically. CGP uses this to describe the *shape* of a struct: the list of its fields, in order, as one type. A type-level list is sometimes called an anonymous product type, because like a tuple it holds several things at once, but unlike a tuple it is built from a recursive `Cons`/`Nil` spine that generic code can take apart one element at a time. - -The list is what makes structural, field-by-field operations possible. Because the fields of a struct are exposed as a single list type through [`HasFields`](../traits/has_fields.md), a provider can be written once to iterate, transform, or rebuild *any* struct's fields without knowing the concrete struct, by recursing over the `Cons`/`Nil` structure. A plain tuple cannot be decomposed this way in generic code; the recursive list can. - -`Product!` and `product!` are two halves of the same idea, split across the type and value levels. `Product!` produces a *type* and is used in type position — associated types, bounds, `type` aliases. `product!` produces a *value* of a matching type and is used in expression position. The uppercase/lowercase convention mirrors Rust's own split between, say, a struct type and a struct literal. - -## Syntax - -Both macros take a comma-separated list of elements and may be empty. `Product!` takes types and is used wherever a type is expected; `product!` takes expressions and is used wherever a value is expected: - -```rust -Product![u32, String, bool] // a type -product![1u32, "hi".to_string(), true] // a value of that type -Product![] // the empty list type -``` - -The element lists line up positionally, so the value built by `product!` has the type built by `Product!` over the corresponding element types. - -## Syntax Grammar - -The two macros take a possibly-empty, comma-separated list — of types for `Product!` and of expressions for `product!`: - -```ebnf -ProductInput -> ( Type ( `,` Type )* `,`? )? - -ProductExpr -> ( Expression ( `,` Expression )* `,`? )? -``` - -`ProductInput` is the grammar of the type macro `Product!`, used in type position; `ProductExpr` is the grammar of the value macro `product!`, used in expression position. `Type` and `Expression` are the Rust grammar's productions, and both lists may be empty (`Product![]`, `product![]`) or carry a trailing comma. The element lists line up positionally, so a `product!` value has the type the corresponding `Product!` builds. - -## Expansion - -`Product!` expands to a right-nested chain of `Cons`, terminated by `Nil`. The three-element list desugars as follows: - -```rust -// before -Product![A, B, C] -``` - -```rust -// after -Cons>> -``` - -The two building blocks are defined in `cgp-base-types`. `Cons` is a pair holding the first element and the rest of the list as `Cons(pub Head, pub Tail)`; chaining it through `Tail` and terminating with the empty `Nil` struct produces a list of any length. An empty `Product![]` is simply `Nil`. The macro constructs the chain by folding the elements from right to left onto `Nil`. - -The value macro `product!` expands the same way but produces a value rather than a type, using the tuple-struct constructor of `Cons`: - -```rust -// before -product![a, b, c] -``` - -```rust -// after -Cons(a, Cons(b, Cons(c, Nil))) -``` - -Because `Cons` is a real tuple struct and `Nil` a real unit struct, the value built by `product!` is an ordinary owned value whose type is exactly what `Product!` builds over the same elements' types. - -## Examples - -The most common appearance of `Product!` is as the `Fields` of a struct that derives [`HasFields`](../derives/derive_has_fields.md), where each element is a `Field` pairing a field name with its type: - -```rust -use cgp::prelude::*; - -#[derive(HasFields)] -pub struct Person { - pub name: String, - pub age: u8, -} - -// generated: -// impl HasFields for Person { -// type Fields = Product![ -// Field, -// Field, -// ]; -// } -``` - -The field names here are type-level strings — see [`Symbol!`](symbol.md) — so the whole `Product!` is a fully type-level description of `Person`'s layout. Generic code can then walk that list to build, read, or transform a `Person` without being written against `Person` specifically. - -A standalone list and a matching value can also be written directly: - -```rust -type Row = Product![u32, String, bool]; -let row: Row = product![1, "hi".to_string(), true]; -``` - -## Related constructs - -`Product!` is the product (record-like) counterpart to [`Sum!`](sum.md), which builds the coproduct used for enum variants; the two share the same right-nested shape but `Sum!` terminates in `Void` rather than `Nil`. The list elements are most often [`Field`](../types/field.md) entries whose tags are [`Symbol!`](symbol.md) field names or [`Index`](../types/index.md) positions. The list type as a whole is what [`#[derive(HasFields)]`](../derives/derive_has_fields.md) assigns to a struct, building on the per-field [`#[derive(HasField)]`](../derives/derive_has_field.md). The `Chars` list inside `Symbol!` is a specialized version of this same `Cons`/`Nil` structure. - -## Source - -- Entry points: `Product` and `product` in [crates/macros/cgp-macro-lib/src/product.rs](../../../crates/macros/cgp-macro-lib/src/product.rs). -- Type form: the `ProductType` construct in [crates/macros/cgp-macro-core/src/types/product/product_type.rs](../../../crates/macros/cgp-macro-core/src/types/product/product_type.rs), whose `eval` right-folds the elements with `Cons` onto `Nil`. -- Value form: `ProductExpr` in [crates/macros/cgp-macro-core/src/types/product/product_expr.rs](../../../crates/macros/cgp-macro-core/src/types/product/product_expr.rs), which does the same fold using the `Cons(..)` constructor. -- Runtime types: [crates/core/cgp-base-types/src/types/cons.rs](../../../crates/core/cgp-base-types/src/types/cons.rs) (`Cons`) and [crates/core/cgp-base-types/src/types/nil.rs](../../../crates/core/cgp-base-types/src/types/nil.rs) (`Nil`). -- Internal walkthrough (the parse-and-`eval` pipeline shared by both forms, the right-fold onto `Nil`, and the index of tests): [implementation/entrypoints/product.md](../../implementation/entrypoints/product.md). diff --git a/docs/reference/macros/sum.md b/docs/reference/macros/sum.md deleted file mode 100644 index ca065179..00000000 --- a/docs/reference/macros/sum.md +++ /dev/null @@ -1,94 +0,0 @@ -# `Sum!` - -`Sum![A, B, C]` is the type macro that builds a type-level sum type — a coproduct encoded in the type system whose value is exactly one of the listed types — used by CGP to represent the variants of an enum the way [`Product!`](product.md) represents the fields of a struct. - -## Purpose - -`Sum!` exists to represent a choice among several types as a single type, so that the variants of an enum can be reasoned about generically. Where a [`Product!`](product.md) list holds a value for *every* element at once (a record), a `Sum!` holds a value for exactly *one* element (a tagged union). It is sometimes called an anonymous sum type or coproduct, and it is the structural mirror image of the product: both are right-nested chains over the same kind of spine, but the sum branches at each step instead of pairing. - -The sum is what makes structural, variant-by-variant operations possible. Because an enum's variants are exposed as a single sum type through [`HasFields`](../traits/has_fields.md), a provider can be written once to match, dispatch on, or construct *any* enum's variants without knowing the concrete enum, by recursing over the nested branch structure. This is the basis for CGP's extensible-variant machinery, where each variant is handled by walking the chain rather than by writing a hand-rolled `match` against a fixed enum. - -`Sum!` is the variant-level analogue of `Product!`, and the two are used together. A struct's fields desugar to a `Product!`; an enum's variants desugar to a `Sum!` of the same `Field` entries. Knowing one shape tells you the other. - -## Syntax - -The macro takes a comma-separated list of types and may be empty. It is used wherever a type is expected: - -```rust -Sum![u32, String, bool] -Sum![] // the empty sum -``` - -Each listed type is one possible variant of the sum; a value of the sum type carries exactly one of them. - -## Syntax Grammar - -The input to `Sum!` is a possibly-empty, comma-separated list of types: - -```ebnf -SumInput -> ( Type ( `,` Type )* `,`? )? -``` - -`Type` is the Rust grammar's type production, and the list may be empty (`Sum![]`) or carry a trailing comma. The macro is used in type position, and each listed type is one possible variant of the sum. - -## Expansion - -`Sum!` expands to a right-nested chain of `Either`, terminated by `Void`. The three-element sum desugars as follows: - -```rust -// before -Sum![A, B, C] -``` - -```rust -// after — readable form -Either>> -``` - -The two building blocks are defined in `cgp-field` and differ from the product spine in being branching rather than pairing. `Either` is the sum cell, an enum with two cases — `Left(Head)` selects the head type, and `Right(Tail)` defers to the rest of the chain — so a value of `Either>>` is `Left` for an `A`, `Right(Left(..))` for a `B`, and `Right(Right(Left(..)))` for a `C`. The terminator is `Void`, an empty enum that can never be constructed, which closes the chain off: reaching the `Void` position would mean the value matched none of the listed types, which is impossible. An empty `Sum![]` is therefore just `Void`, a type with no values. - -The choice of `Void` rather than `Nil` is the key difference from [`Product!`](product.md). A product terminates in `Nil` because an empty record is a valid, constructible value (the unit-like `Nil`); a sum terminates in `Void` because an empty choice is *uninhabited* — there is no value to pick. `Void` is functionally the never type, used here specifically to mark the end of a sum. The macro constructs the chain by folding the element types from right to left onto `Void`. - -## Examples - -The primary appearance of `Sum!` is as the `Fields` of an enum that derives [`HasFields`](../derives/derive_has_fields.md), where each branch is a `Field` pairing a variant name with its payload: - -```rust -use cgp::prelude::*; - -#[derive(HasFields)] -pub enum Shape { - Circle(f64), - Rectangle { width: f64, height: f64 }, -} - -// generated (schematically): -// impl HasFields for Shape { -// type Fields = Sum![ -// Field, -// Field, -// Field, -// ]>, -// ]; -// } -``` - -The variant names are type-level strings — see [`Symbol!`](symbol.md) — and a struct-like variant nests a [`Product!`](product.md) of its own fields, so an enum's full shape is a `Sum!` of variants whose payloads may themselves be `Product!` records. Generic code walks the `Sum!` to dispatch on which variant a value holds, and walks any nested `Product!` to reach that variant's fields. - -A standalone sum type can also be written directly: - -```rust -type Token = Sum![u32, String, bool]; -``` - -## Related constructs - -`Sum!` is the coproduct counterpart to [`Product!`](product.md): the two share a right-nested shape, but `Sum!` branches with [`Either`](../types/either.md) and terminates in the uninhabited `Void`, while `Product!` pairs with [`Cons`](../types/cons.md) and terminates in `Nil`. Its branches are typically [`Field`](../types/field.md) entries whose tags are [`Symbol!`](symbol.md) variant names. The sum type as a whole is what [`#[derive(HasFields)]`](../derives/derive_has_fields.md) assigns to an enum, and it underpins the extensible-variant derives [`#[derive(CgpVariant)]`](../derives/derive_cgp_variant.md) and [`#[derive(FromVariant)]`](../derives/derive_from_variant.md), which build and consume individual `Either` branches. For struct fields, the per-field tags are produced by [`#[derive(HasField)]`](../derives/derive_has_field.md). - -## Source - -- Entry point: `Sum` in [crates/macros/cgp-macro-lib/src/sum.rs](../../../crates/macros/cgp-macro-lib/src/sum.rs), forwarding to the `SumType` construct in [crates/macros/cgp-macro-core/src/types/sum.rs](../../../crates/macros/cgp-macro-core/src/types/sum.rs), whose `eval` right-folds the element types with `Either` onto `Void`. -- Runtime types: `Either` and the uninhabited `Void`, both defined in [crates/core/cgp-field/src/types/sum.rs](../../../crates/core/cgp-field/src/types/sum.rs). -- Enum `HasFields` derive that emits a `Sum!` of `Field` branches: [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/sum.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/sum.rs). -- Internal walkthrough (the parse-and-`eval` pipeline, the right-fold onto `Void`, and the index of tests): [implementation/entrypoints/sum.md](../../implementation/entrypoints/sum.md). diff --git a/docs/reference/macros/symbol.md b/docs/reference/macros/symbol.md deleted file mode 100644 index 029f4621..00000000 --- a/docs/reference/macros/symbol.md +++ /dev/null @@ -1,90 +0,0 @@ -# `Symbol!` - -`Symbol!("...")` is the type macro that turns a string literal into a type-level string — a distinct type, carrying no runtime value, that CGP uses to name a field so the name can be matched and dispatched on at compile time. - -## Purpose - -`Symbol!` exists because CGP needs field *names* to be types, not values. The core getter mechanism, [`HasField`](../traits/has_field.md), is parameterized by a `Tag` type that identifies which field is being read; to look up a field called `name`, something must stand in for the string `"name"` at the type level. `Symbol!("name")` is that something. It produces a unique type whose entire identity is the character sequence it encodes, so two `Symbol!` invocations with the same string are the same type and two with different strings are different types. - -Encoding strings as types is what lets field access participate in trait resolution. Because `Symbol!("width")` is a type, a context can carry one `HasField` impl and another `HasField` impl side by side, and the compiler picks the right one purely from the tag. The same string-as-type trick drives [`#[cgp_auto_getter]`](cgp_auto_getter.md), `UseField`, and the `Field` entries that make up a struct's [`HasFields`](../traits/has_fields.md) representation. Wherever a name needs to be matched at compile time, it appears as a `Symbol!`. - -The type-level string is distinct from the type-level *number* used for tuple fields. Unnamed (tuple) struct fields have no string name, so [`#[derive(HasField)]`](../derives/derive_has_field.md) tags them with the [`Index`](../types/index.md) type instead — `Index<0>`, `Index<1>`, and so on — which encodes a `usize` at the type level the way `Symbol!` encodes a string. A field is keyed by `Symbol!` when it has a name and by `Index` when it has only a position. - -## Syntax - -The macro takes a single string literal and is used wherever a type is expected. It appears in trait bounds, associated-type positions, and `PhantomData` tags: - -```rust -Symbol!("name") -Symbol!("first_name") -Symbol!("") -``` - -Any valid string literal is accepted, including the empty string and multi-byte Unicode (`Symbol!("世界")`), and the macro is most commonly seen inside a `HasField` bound such as `HasField`. - -## Syntax Grammar - -The input to `Symbol!` is a single string literal: - -```ebnf -SymbolInput -> STRING_LITERAL -``` - -`STRING_LITERAL` is the Rust string-literal token, so any valid string literal is accepted — including the empty string and multi-byte Unicode. The macro is used in type position, so this single literal is the whole of its input. - -## Expansion - -`Symbol!("...")` expands to the `Symbol` type wrapping a `Chars` chain that spells out the string one character at a time. The string `"abc"` desugars as follows: - -```rust -// before -Symbol!("abc") -``` - -```rust -// after -Symbol<3, Chars<'a', Chars<'b', Chars<'c', Nil>>>> -``` - -Two type constructors do the work, and both are defined in `cgp-base-types`. `Chars` is a single character paired with the rest of the string; chained through its `Tail` and terminated by `Nil`, it forms a type-level list of characters — the direct analogue of [`Cons`](../types/cons.md)/`Nil` but specialized so the head is a `const char` rather than a type. `Symbol` then wraps that character list together with the string's length. - -The `LEN` parameter is the part of the expansion most likely to surprise a reader, and it exists to work around a limitation in stable Rust. Stable Rust cannot evaluate the length of a `Chars` chain inside a const-generic context, so the macro precomputes the length and bakes it into the type as a separate const parameter rather than deriving it from the character list. The value is the string's byte length — `str::len()` — so `Symbol!("abc")` records `3` and a four-character Chinese string like `Symbol!("世界你好")` records `12`, not `4`. The character list, by contrast, has one `Chars` node per Unicode scalar value. `LEN` lets length-dependent code read the size off the type directly instead of recursing through the list. - -The macro builds the expansion by folding the characters from right to left onto `Nil`, then wrapping the result in `Symbol` with the precomputed length, so an empty string `Symbol!("")` becomes `Symbol<0, Nil>`. - -## Examples - -`Symbol!` most often appears as the tag in a getter bound. The following provider reads a `name` field from any context that exposes one, without that context ever naming the provider: - -```rust -use cgp::prelude::*; - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasField, -{ - fn greet(&self) { - println!("Hello, {}!", self.get_field(PhantomData::)); - } -} -``` - -A type-level string can also be constructed directly and inspected at runtime through its `Display` impl, which reconstructs the original string from the `Chars` chain: - -```rust -let s = ::default(); -assert_eq!(s.to_string(), "hello"); // prints "hello" -``` - -When a struct derives [`HasField`](../derives/derive_has_field.md), each named field's tag is a `Symbol!` of the field name, and the whole struct's [`HasFields`](../traits/has_fields.md) representation is a [`Product!`](product.md) of `Field` entries — so `Symbol!` is the bridge between a field's source name and its type-level identity. The derive tags a field by its *logical* name, stripping the `r#` prefix from a raw identifier: a field written `r#type` is tagged `Symbol!("type")`, the same tag you would write by hand, so `Symbol!("type")` addresses it. The `Symbol!` macro itself takes a string literal verbatim and performs no such stripping, which is why `Symbol!("type")` — not `Symbol!("r#type")` — is the tag that matches an `r#type` field. - -## Related constructs - -`Symbol!` is the field-name half of CGP's tagging scheme; [`Index`](../types/index.md) is the field-position half, used for tuple-struct fields. The tags it produces are consumed by [`HasField`](../traits/has_field.md) for single-field access and by the `Field` entries inside [`HasFields`](../traits/has_fields.md). The character list it builds is a specialized [`Product!`](product.md) list — `Chars`/`Nil` mirror `Cons`/`Nil`. Higher-level getters that take a `Symbol!` tag include [`#[cgp_auto_getter]`](cgp_auto_getter.md) and the `UseField` pattern. For enums, the analogous variant names appear as `Symbol!` tags inside a [`Sum!`](sum.md) representation. - -## Source - -- Entry point: `Symbol` in [crates/macros/cgp-macro-lib/src/symbol.rs](../../../crates/macros/cgp-macro-lib/src/symbol.rs), which forwards to the `Symbol` construct in [crates/macros/cgp-macro-core/src/types/field/symbol.rs](../../../crates/macros/cgp-macro-core/src/types/field/symbol.rs) — its `ToTokens` impl performs the right-fold over the characters, computes `LEN` from `str::len()`, and wraps the result in `Symbol`. -- Runtime types: [crates/core/cgp-base-types/src/types/symbol.rs](../../../crates/core/cgp-base-types/src/types/symbol.rs) (`Symbol`) and [crates/core/cgp-base-types/src/types/chars.rs](../../../crates/core/cgp-base-types/src/types/chars.rs) (`Chars`), with `Nil` in [crates/core/cgp-base-types/src/types/nil.rs](../../../crates/core/cgp-base-types/src/types/nil.rs). -- Internal walkthrough (the parse-and-emit pipeline, the character fold and the `LEN` const workaround, and the index of tests): [implementation/entrypoints/symbol.md](../../implementation/entrypoints/symbol.md). diff --git a/docs/reference/providers/chain_getters.md b/docs/reference/providers/chain_getters.md deleted file mode 100644 index ffc3a5c3..00000000 --- a/docs/reference/providers/chain_getters.md +++ /dev/null @@ -1,103 +0,0 @@ -# `ChainGetters` - -`ChainGetters` is a zero-sized provider that composes a list of field getters into one, navigating from an outer context through a sequence of intermediate values to reach a deeply nested field. - -## Purpose - -`ChainGetters` exists to reach a field that does not live directly on the context but several hops inside it. A single [`UseField`](use_field.md) reads one field of one context. But CGP contexts often nest — a context holds a config, the config holds a connection, the connection holds a timeout — and a getter may need the innermost value. Writing one provider that walks the whole path by hand is tedious and couples the getter to the nesting. `ChainGetters` takes a list of getters, applies them in order, and threads the reference from each step into the next, so the chain reads like the path it traverses: outer getter, then the next, then the next, ending at the target field. - -The list is a type-level [`Cons`](../types/cons.md) spine — `Cons>` — where each element is itself a field getter for the value the previous step produced. `ChainGetters` recurses down this list, so it is naturally written with the [`Product!`](../macros/product.md) macro that builds such spines. Like every CGP provider, `ChainGetters` carries no runtime value; it is a `PhantomData`-only marker named in wiring. - -## Definition - -`ChainGetters` is a phantom-typed struct parameterized by the list of getters it chains, defined in `cgp-field`: - -```rust -pub struct ChainGetters(pub PhantomData); -``` - -`Getters` is a [`Cons`](../types/cons.md)/`Nil` list whose elements are field getters. The provider has no `With...` alias; it is used directly in wiring as the provider for a getter component, or composed inside other getter wiring. `ChainGetters` is not re-exported through `cgp::prelude`; reach it through `cgp::core::field::impls`. - -## Implementations - -`ChainGetters` implements the provider-side getter [`FieldGetter`](../traits/has_field.md) with two impls that together perform the recursion over the list — one for a non-empty `Cons` and one for the empty `Nil`. The `Cons` impl applies the head getter, then delegates the remainder of the path to `ChainGetters` over the tail: - -```rust -impl FieldGetter - for ChainGetters> -where - Getter: FieldMapper, - ChainGetters: FieldGetter, -{ - type Value = ValueB; - - fn get_field(context: &Context, tag: PhantomData) -> &ValueB { - Getter::map_field(context, tag, |value| { - >::get_field(value, tag) - }) - } -} -``` - -The head `Getter` reads `ValueA` from the `Context`, and the rest of the chain — `ChainGetters` — reads `ValueB` from that `ValueA`, so the whole chain's `Value` is `ValueB`, the value at the end of the path. The head is applied through [`FieldMapper`](../traits/has_field.md) rather than `FieldGetter` directly: `map_field` hands the intermediate `ValueA` reference to a closure that runs the rest of the chain on it, which is the construct that keeps the borrowed lifetimes inferring correctly across each hop. Note that every step is asked under the same `Tag` — the chain navigates *contexts*, not different field names per step; each getter in the list decides for itself which field of its input it reads. - -The recursion bottoms out at the empty list, where `ChainGetters` is the identity getter — it returns the context it was given: - -```rust -impl FieldGetter for ChainGetters { - type Value = Context; - - fn get_field(context: &Context, _tag: PhantomData) -> &Context { - context - } -} -``` - -So a chain of one getter resolves to that getter applied to the context (its tail being `Nil` returns the value unchanged), a chain of two applies the first then the second, and so on down the `Cons` spine. - -## Examples - -A typical use reaches a field on a nested inner context by chaining the getter that produces the inner context with the getter that reads the field. Here an `App` holds a `Config`, and the target is the `Config`'s field: - -```rust -use cgp::prelude::*; -use cgp::core::field::impls::ChainGetters; // not re-exported through the prelude - -#[cgp_getter] -pub trait HasPort { - fn port(&self) -> &u16; -} - -#[derive(HasField)] -pub struct Config { - pub port: u16, -} - -#[derive(HasField)] -pub struct App { - pub config: Config, -} - -delegate_components! { - App { - PortGetterComponent: ChainGetters< - Product![ - UseField, - UseField, - ], - >, - } -} -``` - -`App` wires `PortGetterComponent` to `ChainGetters` over a two-element list. The first getter, `UseField`, reads `App`'s `config` field to produce a `&Config`; the second, `UseField`, reads that `Config`'s `port` field to produce the `&u16`. `ChainGetters` threads the reference from the first step into the second, so `app.port()` returns the port nested two levels in, without any hand-written walking code. - -## Related constructs - -`ChainGetters` composes the same provider-side [`FieldGetter`](../traits/has_field.md) that [`UseField`](use_field.md) and [`UseFieldRef`](use_field_ref.md) implement, using each as a step in the path; it applies each step through [`FieldMapper`](../traits/has_field.md) to keep borrowed lifetimes inferring across hops. Its list of getters is a type-level [`Cons`](../types/cons.md)/`Nil` spine, most conveniently written with the [`Product!`](../macros/product.md) macro, and its steps read fields keyed by [`Symbol!`](../macros/symbol.md) on contexts deriving [`#[derive(HasField)]`](../derives/derive_has_field.md). It is wired as the provider for a getter defined with [`#[cgp_getter]`](../macros/cgp_getter.md). - -## Source - -- The `ChainGetters` struct and its two `FieldGetter` impls (the `Cons` recursion step and the `Nil` base case) are in [crates/core/cgp-field/src/impls/chain.rs](../../../crates/core/cgp-field/src/impls/chain.rs). -- The `FieldGetter` and `FieldMapper` traits it builds on are in [crates/core/cgp-field/src/traits/has_field.rs](../../../crates/core/cgp-field/src/traits/has_field.rs) and [crates/core/cgp-field/src/traits/map_field.rs](../../../crates/core/cgp-field/src/traits/map_field.rs). -- The `Cons`/`Nil` spine it recurses over is defined in [crates/core/cgp-base-types/src/types/](../../../crates/core/cgp-base-types/src/types/) (`cons.rs` and `nil.rs`). diff --git a/docs/reference/providers/dispatch_combinators.md b/docs/reference/providers/dispatch_combinators.md deleted file mode 100644 index ddb834ec..00000000 --- a/docs/reference/providers/dispatch_combinators.md +++ /dev/null @@ -1,270 +0,0 @@ -# Dispatch combinators - -The dispatch combinators are the `cgp-dispatch` provider structs that route an extensible-data value — a record or a variant — to per-field or per-variant handlers, and assemble or finalize the result. They are all [`Computer`](../components/computer.md)-family providers (and several are also [`Handler`](../components/handler.md)/`TryComputer` providers) built on top of the extractor and builder trait families. - -## Purpose - -The dispatch combinators solve the problem of handling an arbitrary record or enum generically, where the set of fields or variants is not known at the call site and the handling logic for each one lives in a separate provider. A hand-written `match` on a concrete enum names every variant in one place and calls a fixed function for each; these combinators instead drive the extractor family from [`extract_field`](../traits/extract_field.md) and the builder family from [`has_builder`](../traits/has_builder.md) to do the same work over a value whose shape is only known at the type level, dispatching each field or variant to a handler chosen by type. - -The combinators divide into two halves that mirror the two halves of the extensible-data machinery. The *matcher* half consumes a sum type: it tries each variant in turn, hands the matched payload to a handler, and proves the match exhaustive without a wildcard arm. The *builder* half produces a product type: it starts from an empty builder, runs a handler per field to compute that field's value, and finalizes the fully-populated record. Both halves are expressed as handler providers so they compose with the rest of the handler ecosystem — they can be wired into a context with [`delegate_components!`](../macros/delegate_components.md), nested inside [`UseInputDelegate`](../providers/use_delegate.md), and chained with the combinators in [`handler_combinators`](handler_combinators.md). - -## Definition - -The combinators are zero-sized provider structs, each generic over a list of handlers or a per-element provider, that implement one or more of the handler component traits. This section groups them by role: the matcher loop they share, the matchers that drive it directly, the field/variant adapters that prepare a value for a handler, the convenience matchers that build a handler list from a context's fields, and the builders that go the other way. Every struct verified here carries `PhantomData` over its type parameters and is a pure type-level entity with no runtime value, in keeping with how CGP providers work. - -### `DispatchMatchers` — the matcher loop - -`DispatchMatchers` is the engine every matcher delegates to. It is a type alias for a monadic pipeline that runs a list of handlers and stops at the first one that succeeds: - -```rust -pub type DispatchMatchers = PipeMonadic; -``` - -The list `Providers` is a `Product![...]` of handlers, each of which takes the partial value (the extractor) and returns `Result`: `Ok(output)` means that handler matched and produced the output, while `Err(remainder)` means it did not match and hands back the extractor with one more variant ruled out. [`PipeMonadic`](monad_providers.md) under the `OkMonadic` monad threads the pipeline along the `Err` branch and short-circuits on `Ok`, so the list runs handler by handler until one returns `Ok`, carrying the shrinking remainder forward through each `Err`. When the last handler still returns `Err`, the remainder type has every variant ruled out and is therefore uninhabited, which is what lets the enclosing matcher discharge it without a fallback arm. `DispatchMatchers` is an implementation detail of the matchers below and is not normally named by users. - -### `MatchWithHandlers` and its borrowed forms - -`MatchWithHandlers` is the owned-input matcher: given a value, it converts the value to its extractor and runs the handler list over it. Its `Computer`/`AsyncComputer` impls require the input to implement [`HasExtractor`](../traits/extract_field.md), run `DispatchMatchers` over `Input::Extractor` to obtain `Result`, and call `finalize_extract_result` on that result so the uninhabited remainder is discharged and the bare `Output` is returned: - -```rust -pub struct MatchWithHandlers(pub PhantomData); - -impl Computer - for MatchWithHandlers -where - Input: HasExtractor, - DispatchMatchers: - Computer>, - Remainder: FinalizeExtract, -{ - type Output = Output; - // compute: DispatchMatchers::compute(context, code, input.to_extractor()) - // .finalize_extract_result() -} -``` - -`MatchWithHandlersRef` and `MatchWithHandlersMut` are the same provider over a borrowed input. They implement the handler traits for `&'a Input` and `&'a mut Input` respectively, require `HasExtractorRef`/`HasExtractorMut`, and run the handler list over `Input::ExtractorRef<'a>`/`Input::ExtractorMut<'a>`, so a value can be matched without being moved. Each of the three structs implements both the synchronous `Computer` and the `AsyncComputer` form. - -### `MatchFirstWithHandlers` and its borrowed forms - -`MatchFirstWithHandlers` is the matcher for the multi-argument calling convention, where the input is a tuple `(Input, Args)` carrying the value being matched together with extra arguments to pass along to each handler. It threads `Args` through the loop unchanged: the handler list runs over `(Input::Extractor, Args)` and returns `Result`, so on a miss both the shrunken remainder and the still-owned arguments are carried to the next handler. When the loop finishes with an `Err`, the remainder is uninhabited and is discharged directly through `finalize_extract`: - -```rust -pub struct MatchFirstWithHandlers(pub PhantomData); - -impl - Computer for MatchFirstWithHandlers -where - Input: HasExtractor, - DispatchMatchers: Computer< - Context, Code, (Input::Extractor, Args), - Output = Result>, - Remainder: FinalizeExtract, -{ - type Output = Output; - // compute: match DispatchMatchers::compute(context, code, (input.to_extractor(), args)) { - // Ok(output) => output, - // Err((remainder, _)) => remainder.finalize_extract(), - // } -} -``` - -The "first" in the name reflects that the value being matched is the *first* element of the input tuple, with the remaining arguments riding alongside. As with the plain matcher, there are borrowed variants `MatchFirstWithHandlersRef` and `MatchFirstWithHandlersMut` over `(&'a Input, Args)` and `(&'a mut Input, Args)`, and each struct implements both `Computer` and `AsyncComputer`. - -### The field and variant adapters - -The handler list a matcher runs is normally a list of *adapters*, each of which tries one field or variant and forwards the matched payload to an inner provider. Four adapters cover the matching side, and each pairs a value-extraction step with a delegation to a provider that defaults to [`UseContext`](use_context.md). - -`ExtractFieldAndHandle` is the variant adapter for the owned/value calling convention. Its `Output` is `Result`: it calls `ExtractField` on the input, and on success wraps the payload in a `Field` and hands it to `Provider`, returning `Ok` of the provider's output; on failure it returns `Err` of the remainder, the extractor with that variant ruled out. `ExtractFirstFieldAndHandle` is the same adapter for the `(Input, Args)` convention, threading the arguments into the provider call as `(Field, Args)` and returning `Result`: - -```rust -pub struct ExtractFieldAndHandle(pub PhantomData<(Tag, Provider)>); -pub struct ExtractFirstFieldAndHandle(pub PhantomData<(Tag, Provider)>); -``` - -`HandleFieldValue` and `HandleFirstFieldValue` are the unwrapping adapters that sit between an extract adapter and the actual work. An extract adapter delivers a `Field` so the variant name is still attached to the payload; `HandleFieldValue` strips the `Field` wrapper and passes the bare `Value` to `Provider`, and `HandleFirstFieldValue` does the same for the `(Field, Args)` tuple, forwarding `(Input, Args)`: - -```rust -pub struct HandleFieldValue(pub PhantomData); -pub struct HandleFirstFieldValue(pub PhantomData); -``` - -`DowncastAndHandle` is the adapter that matches a *group* of variants at once rather than a single one. Instead of `ExtractField`, it uses `CanDowncastFields` (see [`cast`](../traits/cast.md)) to try to narrow the input to a smaller enum type `Inner`; on success it hands the whole `Inner` value to `Provider` and returns `Ok`, and on failure it returns `Err` of the remainder. This lets a matcher delegate several variants to one sub-matcher in a single step: - -```rust -pub struct DowncastAndHandle(pub PhantomData<(Input, Provider)>); -``` - -Each of these adapters implements both `Computer` and `AsyncComputer`. Because every one returns the `Result` (or `Result`) shape that `DispatchMatchers` expects, a `Product!` of them is exactly the handler list a matcher consumes. - -### `MatchWithValueHandlers` and `MatchWithFieldHandlers` - -Writing out the full handler list for every variant is mechanical, so the convenience matchers build the list automatically from the input type's own field list. `MatchWithFieldHandlers` and `MatchWithValueHandlers` are type aliases over [`UseInputDelegate`](use_delegate.md) that dispatch on the input type and, for each input, synthesize the per-variant handler list from that input's [`HasFields`](../traits/has_fields.md): - -```rust -pub type MatchWithFieldHandlers = - UseInputDelegate>; - -pub type MatchWithValueHandlers = - UseInputDelegate>>; -``` - -The difference between the two is exactly one `HandleFieldValue` wrapper. `MatchWithFieldHandlers` builds a list of `ExtractFieldAndHandle` adapters, so `Provider` receives each matched payload as a `Field` with the variant name still attached. `MatchWithValueHandlers` wraps `Provider` in `HandleFieldValue` first, so `Provider` receives the bare `Value` — this is the form to use when the per-variant handler is an ordinary computer over the payload type, such as one generated by [`#[cgp_computer]`](../macros/cgp_computer.md). The list itself is assembled by the `HasFieldHandlers`/`ToFieldHandlers` machinery described below. - -The borrowed counterparts are `MatchWithFieldHandlersRef`/`MatchWithValueHandlersRef` (over `&Input`) and `MatchWithValueHandlersMut` (over `&mut Input`). These additionally wire the `…RefComponent` handler traits through [`PromoteRef`](handler_combinators.md), so a single struct serves both the by-value-of-reference and the by-reference handler interfaces. The first-argument convenience matchers — `MatchFirstWithFieldHandlers`, `MatchFirstWithValueHandlers`, and their `Ref`/`Mut` variants — are the same aliases built on `ExtractFirstFieldAndHandle`, `HandleFirstFieldValue`, and the `MatchFirstWith…` matchers, for the `(Input, Args)` calling convention. - -### `ToFieldHandlers`, `HasFieldHandlers`, and `MapFieldHandler` - -The convenience matchers turn a context's field list into a handler list through three cooperating traits. `MapFieldHandler` is a type-level function from a field's `Tag` to the adapter that should handle it; `ToFieldHandlers` walks the [`Either`](../types/either.md)-spine of a field list and applies that function to each field, producing a `Cons`-list of adapters; and `HasFieldHandlers` is the convenience entry point that reads a context's `HasFields::Fields` and runs `ToFieldHandlers` over it: - -```rust -pub trait MapFieldHandler { - type FieldHandler; -} - -pub trait ToFieldHandlers { - type Handlers; -} - -pub trait HasFieldHandlers { - type Handlers; -} - -impl HasFieldHandlers for Context -where - Context: HasFields, - Fields: ToFieldHandlers, -{ - type Handlers = Fields::Handlers; -} -``` - -`ToFieldHandlers` is implemented inductively over the sum spine: for `Either, RestFields>` it produces `Cons, RestFields::Handlers>`, and for the terminating [`Void`](../types/either.md) it produces `Nil`. The two `MapFieldHandler` markers supplied by the crate are `MapExtractFieldAndHandle`, whose `FieldHandler` is `ExtractFieldAndHandle`, and `MapExtractFirstFieldAndHandle`, whose `FieldHandler` is `ExtractFirstFieldAndHandle`. Composing these is how `MatchWithValueHandlers` ends up running an `ExtractFieldAndHandle>` for each variant of the input enum without the user spelling out the list. - -### `BuildAndSetField` and `BuildAndMerge` - -The builder side runs in the opposite direction: rather than taking a value apart, it assembles a record field by field, with one handler computing each field's value. `BuildAndSetField` is the single-field builder adapter. It takes a builder (a partial record from [`has_builder`](../traits/has_builder.md)), runs `Provider` over a *reference* to that builder to compute the value for `Tag`, then calls `BuildField` to set that field and returns the advanced builder. Because the provider sees `&Builder`, it can read fields already set on the partial record while computing the next one: - -```rust -pub struct BuildAndSetField(pub PhantomData<(Tag, Provider)>); - -impl Computer - for BuildAndSetField -where - Provider: for<'a> Computer, - Builder: BuildField, -{ - type Output = Output; - // compute: let value = Provider::compute(context, code, &builder); - // builder.build_field(PhantomData::, value) -} -``` - -`BuildAndMerge` is the bulk counterpart. Instead of setting one field, it runs `Provider` over a reference to the builder to produce another record's worth of fields, then uses `CanBuildFrom` (see [`has_builder`](../traits/has_builder.md)) to copy every shared field from that result into the builder in one step — the field-list analogue of `BuildAndSetField`: - -```rust -pub struct BuildAndMerge(pub PhantomData); - -impl Computer - for BuildAndMerge -where - Provider: for<'a> Computer, - Builder: CanBuildFrom, -{ - type Output = Output; - // compute: let output = Provider::compute(context, code, &builder); - // builder.build_from(output) -} -``` - -Both `BuildAndSetField` and `BuildAndMerge` implement `Computer`, `TryComputer`, and `Handler`; the `TryComputer` and `Handler` forms additionally require `Context: HasErrorType` and propagate the inner provider's error. - -### `BuildWithHandlers` and `BuildAndMergeOutputs` - -`BuildWithHandlers` is the entry point that turns a list of builder adapters into a complete record. It starts from `Output::builder()` (an empty partial record, via `HasBuilder`), pipes that builder through the handler list with [`PipeHandlers`](handler_combinators.md) so each adapter sets its field, and calls `finalize_build` on the fully-populated result to recover the concrete `Output`: - -```rust -pub struct BuildWithHandlers(pub PhantomData<(Output, Handlers)>); - -impl Computer - for BuildWithHandlers -where - Output: HasBuilder, - PipeHandlers: Computer, - Res: FinalizeBuild, -{ - type Output = Output; - // compute: PipeHandlers::compute(context, code, Output::builder()).finalize_build() -} -``` - -The original `Input` is discarded — `BuildWithHandlers` produces its output from the builder, not from the input. It implements `Computer`, `TryComputer`, and `Handler`, with the latter two requiring `Context: HasErrorType`. Because `finalize_build` is in scope only for the all-present builder configuration, omitting a handler for some field is a compile error rather than a runtime failure. - -`BuildAndMergeOutputs` is a higher-level wrapper used when the handler list is itself a list of plain field-producing providers rather than builder adapters. It is a `delegate_components!` table that maps the whole handler family (`ComputerComponent`, `TryComputerComponent`, `HandlerComponent`, and their `Ref` forms) to `BuildWithHandlers`, where each provider in `Handlers` has first been wrapped in `BuildAndMerge` by mapping the list through the `ToBuildAndMergeHandler` [`MapType`](../traits/map_type.md) marker. In effect it lets a caller supply a list of result-producing providers and have each one merged into the builder automatically. - -## Examples - -A matcher is normally driven by spelling out one extract adapter per variant and wrapping each payload handler in `HandleFieldValue`. The following dispatches a `Shape` enum to a per-variant area computer: - -```rust -use cgp::extra::dispatch::{ - ExtractFieldAndHandle, HandleFieldValue, MatchWithHandlers, -}; - -#[derive(CgpData)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} - -// ComputeArea is a #[cgp_computer] over the payload types Circle and Rectangle. -let circle = Shape::Circle(Circle { radius: 5.0 }); - -let _area = MatchWithHandlers::< - Product![ - ExtractFieldAndHandle>, - ExtractFieldAndHandle>, - ], ->::compute(&(), PhantomData::<()>, circle); -``` - -The list tries `Circle` first; if the runtime value is a circle, `ComputeArea` runs on the `Circle` payload and the loop stops. Otherwise the remainder carries `Circle` ruled out into the `Rectangle` adapter, which is the last arm, so its failure would leave an uninhabited remainder that `finalize_extract_result` discharges. - -The same dispatch is far shorter through `MatchWithValueHandlers`, which builds that list from the enum's own fields. Wiring it into a context's `Computer` component with [`UseInputDelegate`](use_delegate.md) lets the matcher be selected when the input is a `Shape`: - -```rust -delegate_components! { - App { - ComputerComponent: UseInputDelegate, - } -} -``` - -Here a `Circle` input is handled directly by `ComputeArea`, while a `Shape` input is handled by `MatchWithValueHandlers`, which synthesizes `ExtractFieldAndHandle>` for each variant and routes each payload back through the context's own `ComputerComponent` — so `Circle` and `Rectangle` payloads reach `ComputeArea` after all. - -The builder side mirrors this. The following assembles a `FooBarBaz` by merging a built `FooBar` and computing the remaining `baz` field: - -```rust -use cgp::extra::dispatch::{BuildAndMerge, BuildAndSetField, BuildWithHandlers}; - -type Handlers = Product![ - BuildAndMerge, - BuildAndSetField, -]; - -let foo_bar_baz = BuildWithHandlers::::compute(&context, code, ()); -``` - -`BuildWithHandlers` starts from `FooBarBaz::builder()`, runs `BuildAndMerge` to copy the `foo` and `bar` fields from a built `FooBar`, runs `BuildAndSetField` to compute and set `baz`, and finalizes. Dropping either handler leaves a field unset and fails to compile at `finalize_build`. - -## Related constructs - -The matchers stand on the enum-deconstruction traits in [`extract_field`](../traits/extract_field.md) (`HasExtractor`, `ExtractField`, `FinalizeExtract`) and obtain the variant list from [`has_fields`](../traits/has_fields.md); the builders stand on the record-assembly traits in [`has_builder`](../traits/has_builder.md) (`HasBuilder`, `BuildField`, `FinalizeBuild`, `CanBuildFrom`). The matcher loop is [`PipeMonadic`](monad_providers.md) under the `OkMonadic` monad, and the builder pipeline is [`PipeHandlers`](handler_combinators.md); both produce providers in the [`Computer`](../components/computer.md)/[`Handler`](../components/handler.md) families. The convenience matchers dispatch through [`UseInputDelegate`](use_delegate.md) and reuse the borrowed-input promotion of [`PromoteRef`](handler_combinators.md), and their per-element provider defaults to [`UseContext`](use_context.md). The grouped-variant adapter `DowncastAndHandle` relies on [`cast`](../traits/cast.md). The high-level overview that ties all of these together is [`dispatching`](../../concepts/dispatching.md), and the attribute macro that generates a matcher-backed trait impl automatically is [`#[cgp_auto_dispatch]`](../macros/cgp_auto_dispatch.md). The data-type patterns these combinators serve are [extensible records](../../concepts/extensible-records.md) (the builders) and [extensible variants](../../concepts/extensible-variants.md) (the matchers); `BuildAndMergeOutputs` drives the [application builder](../../examples/application-builder.md) example, while `MatchWithValueHandlers` and the explicit `MatchWithHandlers` form drive the [extensible shapes](../../examples/extensible-shapes.md) example over a non-recursive enum and the [expression interpreter](../../examples/expression-interpreter.md) example over a recursive one. - -## Source - -- The provider structs live under [crates/extra/cgp-dispatch/src/providers/](../../../crates/extra/cgp-dispatch/src/providers/): the matchers in `with_handlers/` (`match_with_handlers.rs`, `match_with_handlers_ref.rs`, `match_with_handlers_mut.rs`, `match_first_with_handlers*.rs`, `build_with_handlers.rs`), the matcher loop alias in `dispatchers/dispatch_matchers.rs`, the field/variant adapters in `field_matchers/` (`extract_field.rs`, `extract_first_field.rs`, `extract_handle.rs`, `field_value.rs`, `first_field_value.rs`), the convenience matchers and the `ToFieldHandlers`/`HasFieldHandlers`/`MapFieldHandler` machinery in `matchers/` (`match_with_field_handlers.rs`, `match_first_with_field_handlers.rs`, `to_field_handlers.rs`), and the builders in `field_builders/` (`build_and_set_field.rs`, `build_and_merge.rs`) and `builders/build_and_merge_outputs.rs`. -- The prelude re-exports the value-handler matchers from [crates/main/cgp-extra/src/prelude.rs](../../../crates/main/cgp-extra/src/prelude.rs); the remaining structs are reached through `cgp::extra::dispatch`. diff --git a/docs/reference/providers/error_providers.md b/docs/reference/providers/error_providers.md deleted file mode 100644 index 91617422..00000000 --- a/docs/reference/providers/error_providers.md +++ /dev/null @@ -1,194 +0,0 @@ -# In-tree error providers - -The in-tree error providers are the zero-sized provider structs in `cgp-error-extra` that implement the error-raising and error-wrapping components for any context, independent of which concrete error type that context has chosen. - -## Purpose - -These providers exist so that a context can wire up common error-handling strategies without depending on a specific error library. The [`CanRaiseError`](../components/can_raise_error.md) and `CanWrapError` components define *what* a context can do with errors — turn a foreign error into its abstract `Self::Error`, or attach detail to an existing one — but they say nothing about *how*. The providers here supply the how for the cases that do not need a particular backend: convert through `From`, return an error that already is the abstract type, format a foreign error into a string, discard a detail, panic, or absorb an impossible error. Each is generic over the context, so it works with whatever error type the context's [`HasErrorType`](../components/has_error_type.md) names. - -These are the in-tree counterparts to the standalone error backends in `cgp-error-anyhow`, `cgp-error-eyre`, and `cgp-error-std`. Those crates supply providers specialized to a concrete error type — raising into an `anyhow::Error`, for example — whereas the providers in `cgp-error-extra` stay abstract over the context's error type and capture strategies that are independent of any one library. A context typically wires a mix: a backend for the concrete error type plus these generic providers for the cross-cutting strategies. - -Each provider implements one or both of the two error components through their provider traits. `CanRaiseError`'s provider trait is `ErrorRaiser`, wired to the context with `ErrorRaiserComponent`; `CanWrapError`'s provider trait is `ErrorWrapper`, wired with `ErrorWrapperComponent`. A provider that implements `ErrorRaiser` supplies raising; one that implements `ErrorWrapper` supplies wrapping; some supply both. - -## Implementations - -The seven providers divide into three groups by which component they implement and how they treat the error. The pure raisers — `RaiseFrom`, `ReturnError`, and `RaiseInfallible` — implement `ErrorRaiser` to convert a source error into the abstract error. `DiscardDetail` implements `ErrorWrapper` to drop detail. `PanicOnError` implements `ErrorRaiser` to abort rather than produce an error value. The string-formatting providers `DebugError` and `DisplayError` implement both components, redirecting through a string. The following sections describe each, naming the component it provides and the bound it places on the context or the error. - -### `RaiseFrom` — convert via `From` - -`RaiseFrom` is the `ErrorRaiser` provider that raises a source error by converting it into the context's error type with the standard `From` trait. It implements `ErrorRaiser` for any context whose abstract `Error` implements `From`: - -```rust -#[cgp_new_provider] -impl ErrorRaiser for RaiseFrom -where - Context: HasErrorType, - Context::Error: From, -{ - fn raise_error(e: E) -> Context::Error { - e.into() - } -} -``` - -This is the default choice whenever the abstract error already knows how to absorb the source error through `From`. Because the bound is `Context::Error: From`, a single wiring of `RaiseFrom` covers every source error type the context's error has a `From` impl for. - -### `ReturnError` — the source is already the abstract error - -`ReturnError` is the `ErrorRaiser` provider for the case where the source error *is* the context's abstract error, so raising is the identity. It implements `ErrorRaiser` only when the context's `Error` is exactly `E`: - -```rust -#[cgp_new_provider] -impl ErrorRaiser for ReturnError -where - Context: HasErrorType, -{ - fn raise_error(e: E) -> E { - e - } -} -``` - -The `HasErrorType` bound ties the source type to the abstract error, so `raise_error` returns its argument untouched. This is what a context uses when generic code raises a value that is already of the context's chosen error type. - -### `RaiseInfallible` — absorb an impossible error - -`RaiseInfallible` is the `ErrorRaiser` provider for `core::convert::Infallible`, the error type that can never be constructed. It implements `ErrorRaiser` for any context with an error type, producing the abstract error by matching on the uninhabited value: - -```rust -#[cgp_new_provider] -impl ErrorRaiser for RaiseInfallible -where - Context: HasErrorType, -{ - fn raise_error(e: Infallible) -> Context::Error { - match e {} - } -} -``` - -Since an `Infallible` value cannot exist, the empty `match` is total and the function is never actually called at runtime. This provider lets generic code that is parameterized over a fallible operation be wired uniformly even when the operation chosen for a given context cannot fail. - -### `DiscardDetail` — wrap by ignoring the detail - -`DiscardDetail` is the `ErrorWrapper` provider that throws away whatever detail is attached and returns the error unchanged. It implements `ErrorWrapper` for any context and any detail type: - -```rust -#[cgp_new_provider] -impl ErrorWrapper for DiscardDetail -where - Context: HasErrorType, -{ - fn wrap_error(error: Context::Error, _detail: Detail) -> Context::Error { - error - } -} -``` - -This satisfies the `CanWrapError` capability without actually enriching the error, which is useful when a context's error type cannot carry extra context, or when the wrapping detail is deliberately not retained. It is the wrapping counterpart to a no-op: the error propagates as-is. - -### `PanicOnError` — abort instead of producing an error - -`PanicOnError` is the `ErrorRaiser` provider that panics with the source error's debug representation rather than returning an abstract error. It implements `ErrorRaiser` for any context whose error type exists, requiring only that the source error is `Debug`: - -```rust -#[cgp_new_provider] -impl ErrorRaiser for PanicOnError -where - Context: HasErrorType, - E: Debug, -{ - fn raise_error(e: E) -> Context::Error { - panic!("{e:?}") - } -} -``` - -Although the signature promises to return `Context::Error`, the body never does — `panic!` diverges. This provider is for contexts where an error is treated as a programming fault that should abort rather than be handled, such as tests or fail-fast tooling. - -### `DebugError` and `DisplayError` — format through a string - -`DebugError` and `DisplayError` implement *both* error components by redirecting through the context's string-based error handling. Rather than producing the abstract error directly, each formats the source error or detail into a `String` and forwards to the context's own `CanRaiseError` or `CanWrapError` — so they delegate the final step to whatever string-handling provider the context already wires. `DebugError` formats with the `Debug` trait: - -```rust -#[cgp_provider] -impl ErrorRaiser for DebugError -where - Context: CanRaiseError, - E: Debug, -{ - fn raise_error(e: E) -> Context::Error { - Context::raise_error(format!("{e:?}")) - } -} - -#[cgp_provider] -impl ErrorWrapper for DebugError -where - Context: CanWrapError, - Detail: Debug, -{ - fn wrap_error(error: Context::Error, detail: Detail) -> Context::Error { - Context::wrap_error(error, format!("{detail:?}")) - } -} -``` - -`DisplayError` is identical in shape but formats with the `Display` trait and `to_string()` instead, raising `Context::raise_error(e.to_string())` and wrapping `Context::wrap_error(error, detail.to_string())`. Both require the context to already raise and wrap `String`, which is the indirection that lets them reduce any `Debug` or `Display` error to the string case the context knows how to handle. Because they format into an allocated `String`, both live behind the crate's `alloc` feature. - -## Behavior - -A context gains an error strategy by wiring one of these providers to `ErrorRaiserComponent` or `ErrorWrapperComponent` exactly like any other component, and the provider's `where` clause is what determines when that wiring type-checks. `RaiseFrom` requires the abstract error to be `From` the source, `ReturnError` requires the source to be the abstract error itself, `RaiseInfallible` accepts only `Infallible`, and `PanicOnError` accepts any `Debug` source — so the choice of provider is also a statement about which source errors a context will accept and how. Because both components dispatch through `UseDelegate` over the source-error or detail type, a context commonly wires several of these providers at once through a delegation table, one per source error type. - -The string-formatting providers compose with the other raisers rather than replacing them, which is the key to their design. `DebugError` and `DisplayError` do not know the context's error type; they only know how to turn a `Debug` or `Display` value into a `String` and hand it off. The context must separately wire a provider — often `RaiseFrom` or a backend provider — that handles the `String` source, and the formatting providers route every other source error through that single string path. This lets a context handle an open-ended set of error types with one concrete string-raising rule plus a uniform formatting redirect. - -## Examples - -Wiring `RaiseFrom` to the error-raiser component lets a context raise any source error its abstract error can absorb through `From`: - -```rust -use cgp::prelude::*; -use cgp::core::error::ErrorRaiserComponent; -use cgp::extra::error::RaiseFrom; - -delegate_components! { - App { - ErrorRaiserComponent: - RaiseFrom, - } -} -``` - -With this wiring, any generic provider that calls `Context::raise_error(source)` on `App` succeeds for every `source` whose type the `App` error implements `From` for. - -A context can combine the formatting and converting providers by dispatching per source-error type, so that a `String` is raised directly while other `Debug` errors are formatted into a string first: - -```rust -use cgp::prelude::*; -use cgp::core::error::ErrorRaiserComponent; -use cgp::extra::error::{DebugError, RaiseFrom}; - -delegate_components! { - App { - ErrorRaiserComponent: - UseDelegate, - } -} -``` - -Here a raised `String` is converted straight into the abstract error by `RaiseFrom`, while a raised `ParseError` is formatted with `Debug` by `DebugError` and then forwarded back through the `String` entry — which `RaiseFrom` handles — giving a single coherent error type from two different sources. - -## Related constructs - -These providers implement the [`CanRaiseError`](../components/can_raise_error.md) and `CanWrapError` components through their `ErrorRaiser` and `ErrorWrapper` provider traits, and every one of them is generic over a context that supplies [`HasErrorType`](../components/has_error_type.md) to name the abstract `Self::Error` they produce. They are wired to a context with `delegate_components!` on `ErrorRaiserComponent`/`ErrorWrapperComponent` and checked with `check_components!`, and because both components derive `UseDelegate`, they are typically dispatched per source-error or detail type through a delegation table — see [`UseDelegate`](use_delegate.md). The library-specific counterparts that raise into a concrete error type live in the standalone backends `cgp-error-anyhow`, `cgp-error-eyre`, and `cgp-error-std`. The [modular error handling](../../concepts/modular-error-handling.md) concept explains how these providers fit alongside the abstract error type and the backends as interchangeable error-handling strategies. - -## Source - -- The providers are defined in `cgp-error-extra`: `RaiseFrom` in [crates/extra/cgp-error-extra/src/impls/raise_from.rs](../../../crates/extra/cgp-error-extra/src/impls/raise_from.rs), `ReturnError` in [return_error.rs](../../../crates/extra/cgp-error-extra/src/impls/return_error.rs), `RaiseInfallible` in [infallible.rs](../../../crates/extra/cgp-error-extra/src/impls/infallible.rs), `DiscardDetail` in [discard_detail.rs](../../../crates/extra/cgp-error-extra/src/impls/discard_detail.rs), `PanicOnError` in [panic_error.rs](../../../crates/extra/cgp-error-extra/src/impls/panic_error.rs), and `DebugError`/`DisplayError` (behind the `alloc` feature) in [impls/alloc/debug_error.rs](../../../crates/extra/cgp-error-extra/src/impls/alloc/debug_error.rs) and [display_error.rs](../../../crates/extra/cgp-error-extra/src/impls/alloc/display_error.rs). -- The `ErrorRaiser`/`ErrorWrapper` provider traits and the `CanRaiseError`/`CanWrapError`/`HasErrorType` consumer traits they build on are in [crates/core/cgp-error/src/](../../../crates/core/cgp-error/src/). -- The standalone backend counterparts are in [crates/standalone/error/](../../../crates/standalone/error/). diff --git a/docs/reference/providers/handler_combinators.md b/docs/reference/providers/handler_combinators.md deleted file mode 100644 index e3d5f108..00000000 --- a/docs/reference/providers/handler_combinators.md +++ /dev/null @@ -1,221 +0,0 @@ -# Handler combinators - -The handler combinators are the provider structs of `cgp-handler` that build, sequence, and adapt handlers — composing two providers end to end, threading a whole list through a pipeline, returning the input unchanged, and lifting a provider written for one handler shape into another. - -## Purpose - -The handler combinators exist because the handler family is not one trait but several closely related ones — [`Computer`](../components/computer.md), [`TryComputer`](../components/try_computer.md), `AsyncComputer`, [`Handler`](../components/handler.md), and [`Producer`](../components/producer.md), each with a `…Ref` variant — and code is rarely written against all of them at once. A provider author writes a plain synchronous `Computer`, or a fallible `TryComputer`, or an async `Handler`, depending on what is natural for the computation. The combinators let those single-shape providers be wired wherever a different shape is expected, and let several providers be glued into a larger one, without forcing the author to re-implement each provider across every trait in the family. - -They divide into three jobs. The *composition* combinators (`ComposeHandlers`, `PipeHandlers`) sequence handlers so that the output of one becomes the input of the next. The *identity* combinator (`ReturnInput`) is the neutral element of that composition, passing its input straight through. The *promotion* combinators (`Promote` and friends) lift a provider from one handler trait to another — from a `Computer` up to a `TryComputer`, from a synchronous computer to an async one, from a value handler to a reference handler — so that one written implementation satisfies many traits. Because every combinator is itself a CGP provider, all of this happens at the type level through delegation, and the combinators nest freely inside one another. - -Like all CGP providers, these combinators are zero-sized: their type parameters are inner providers carried in `PhantomData`, never runtime values. The combinator names the providers to compose or promote, and the method bodies forward to those providers' associated functions. - -## The handler family - -Every combinator below is defined in terms of the handler component traits, so a brief orientation helps. The family shares a common method signature: a context reference, a `PhantomData` tag selecting the operation, and an input, producing an associated `Output`. The members differ in fallibility and asynchrony. [`Computer`](../components/computer.md) is the plain synchronous, infallible form, with `compute(context, code, input) -> Output`. [`TryComputer`](../components/try_computer.md) is synchronous but fallible, returning `Result` and requiring `Context: HasErrorType`. `AsyncComputer` is asynchronous and infallible. [`Handler`](../components/handler.md) is asynchronous and fallible, the most general member. Each of these has a `…Ref` companion (`ComputerRef`, `TryComputerRef`, `AsyncComputerRef`, `HandlerRef`) whose method takes the input by reference (`&Input`) instead of by value. [`Producer`](../components/producer.md) is the degenerate case that takes no input at all, producing an `Output` from the context and `Code` alone. - -The promotion combinators trade on the natural orderings among these. A `Computer` is also a valid `TryComputer` (wrap the output in `Ok`) and a valid `AsyncComputer` (the future is ready immediately); a `TryComputer` is a valid `Handler`; a value handler can serve a reference handler by dereferencing. The combinators encode exactly these one-directional lifts. - -## Composing handlers in sequence - -`ComposeHandlers` runs two handlers back to back, feeding the output of the first as the input of the second, and is the fundamental sequencing combinator. It implements every member of the handler family by threading the intermediary value through both providers under the same context and `Code`: - -```rust -pub struct ComposeHandlers(pub PhantomData<(ProviderA, ProviderB)>); -``` - -For the plain `Computer` shape, the impl requires `ProviderA: Computer` and `ProviderB: Computer` — the second provider's input type is pinned to the first's output type — and the composite `Output` is `ProviderB::Output`: - -```rust -impl Computer - for ComposeHandlers -where - ProviderA: Computer, - ProviderB: Computer, -{ - type Output = ProviderB::Output; - - fn compute(context: &Context, code: PhantomData, input: Input) -> Self::Output { - let intermediary = ProviderA::compute(context, code, input); - ProviderB::compute(context, code, intermediary) - } -} -``` - -The same shape is implemented for `TryComputer`, `AsyncComputer`, and `Handler`. The fallible and async variants differ only in how the intermediary is obtained: `TryComputer` and `Handler` use `?` to short-circuit on the first provider's error (and so require `Context: HasErrorType`), and `AsyncComputer` and `Handler` `.await` each step. In every case the two providers share one context and one `Code`; only the value flowing between them changes type. - -## Composing a list of handlers - -`PipeHandlers` generalizes `ComposeHandlers` from two providers to a type-level list of them, composing the whole pipeline right to left into a single nested `ComposeHandlers`. It is parameterized by a [`Product!`](../macros/product.md) list of providers: - -```rust -pub struct PipeHandlers(pub PhantomData); -``` - -`PipeHandlers` carries no handler impls of its own. Instead it delegates every component to whatever single provider the list folds down to, computed by an internal `ComposeProviders` trait that walks the `Cons`/`Nil` list. A list of one provider folds to that provider unchanged; a list `Cons` folds to `ComposeHandlers`. The delegation entry then routes any handler component on `PipeHandlers` to that folded provider: - -```rust -delegate_components! { - > - PipeHandlers { - Component: Provider, - } -} -``` - -The practical effect is that `PipeHandlers` behaves exactly as `ComposeHandlers>`, threading the input through `A`, then `B`, then `C`, with each stage's output type feeding the next stage's input type. Because the delegation is generic over the `Component` key, the same pipeline simultaneously serves as a `Computer`, `TryComputer`, `AsyncComputer`, or `Handler` — whichever the wiring asks for — provided every stage supports that shape. This is the combinator to reach for when wiring a multi-stage transformation: list the stages in order and let `PipeHandlers` build the composition. - -## Returning the input unchanged - -`ReturnInput` is the identity handler: it ignores the context and `Code` and returns its input as its output. It is a plain unit struct with no type parameters: - -```rust -pub struct ReturnInput; -``` - -It implements `Computer`, `TryComputer`, `AsyncComputer`, and `Handler`, in each case setting `Output = Input` and returning the input directly (wrapped in `Ok` for the fallible variants, which therefore require `Context: HasErrorType`). `ReturnInput` is the neutral element of handler composition: composing it before or after any other handler leaves that handler's behavior unchanged. It is useful as a placeholder stage, as the base case of a conditionally-built pipeline, or wherever a handler slot must be filled but no transformation is wanted. - -## Promoting a provider to another handler shape - -The promotion combinators each take a single inner `Provider` and re-expose it under a different member of the handler family, so that an implementation written once satisfies several traits. Each is a one-parameter struct carrying the inner provider in `PhantomData`, and each implements the *target* traits in terms of the inner provider's *source* trait. The lifts they perform are summarized here and detailed below. - -`Promote` lifts upward along the infallible-to-fallible and sync-to-async axes, treating a less capable provider as a more capable one without adding error or async behavior of its own. It gives three impls: - -```rust -pub struct Promote(pub PhantomData); -``` - -As a `Computer`, `Promote` requires the inner `Provider: Producer` and ignores its own input, calling `Provider::produce` — this is how a producer (which takes no input) is adapted to fill a computer slot (which is handed an input it does not need). As a `TryComputer`, it requires `Provider: Computer` and wraps the infallible result in `Ok`. As a `Handler`, it requires `Provider: AsyncComputer` and wraps the awaited result in `Ok`. In each case the promotion adds the missing capability — discarding an input, introducing an always-`Ok` result — without changing what the inner provider computes. - -`PromoteAsync` lifts a synchronous provider into an asynchronous one: - -```rust -pub struct PromoteAsync(pub PhantomData); -``` - -As an `AsyncComputer`, it requires `Provider: Computer` and runs it synchronously inside the async method (the returned future is immediately ready). As a `Handler`, it requires `Provider: TryComputer` and returns that fallible synchronous result, so a synchronous fallible computer becomes an async fallible handler. - -`PromoteRef` bridges between value handlers and reference handlers by dereferencing, and is the most thoroughly implemented promotion — it covers all four families in both directions: - -```rust -pub struct PromoteRef(pub PhantomData); -``` - -For each of `Computer`/`ComputerRef`, `TryComputer`/`TryComputerRef`, `AsyncComputer`/`AsyncComputerRef`, and `Handler`/`HandlerRef`, `PromoteRef` provides two impls. One direction implements the by-value trait given an inner by-reference provider plus `Input: Deref`, calling the inner provider on `input.deref()`. The other direction implements the by-reference trait given an inner by-value provider that works `for<'a>` over `&'a Input`, calling the inner provider on the borrowed input. This lets a provider written to take `&T` serve a slot that hands it a smart pointer to `T`, and vice versa, without manual deref boilerplate. - -`TryPromote` lifts in both directions across the boundary between a `Result`-valued output and a fallible trait, unifying the two ways of expressing fallibility: - -```rust -pub struct TryPromote(pub PhantomData); -``` - -As a `TryComputer`, it requires the inner `Provider: Computer` whose `Output` is itself a `Result`, and unwraps that into the `TryComputer` result — turning a computer that *returns* a `Result` into a genuine fallible computer. As a `Computer`, it goes the other way: given `Provider: TryComputer`, its output type is `Result` and it surfaces the fallible result as an ordinary value. The analogous pair lifts between `Handler` (from an `AsyncComputer` returning a `Result`) and `AsyncComputer` (from a `Handler`). All four impls require `Context: HasErrorType`. - -## Promotion bundles - -Several promotion adapters are not handler impls themselves but delegation tables that wire a whole cluster of handler components to the right single-trait promotion at once. They exist so that a provider author can implement just one trait — say `Computer` — and have the bundle fill in every other member of the family by promotion. Each is defined with [`delegate_components!`](../macros/delegate_components.md) over a generic inner `Provider`, and is what the [`#[cgp_computer]`](../macros/cgp_computer.md) and [`#[cgp_producer]`](../macros/cgp_producer.md) macros wire their generated providers into. - -`PromoteComputer` starts from a provider that implements `Computer` (the by-value, synchronous, infallible base) and fills in every other family member. It routes `TryComputerComponent` to `Promote` (wrap in `Ok`), `AsyncComputerComponent` and `HandlerComponent` to `PromoteAsync` (run synchronously in an async method), and all the `…Ref` components to `PromoteRef` (dereference, then defer to the base): - -```rust -delegate_components! { - - new PromoteComputer { - ComputerRefComponent: PromoteRef, - TryComputerComponent: Promote, - TryComputerRefComponent: PromoteRef, - AsyncComputerComponent: PromoteAsync, - AsyncComputerRefComponent: PromoteRef, - HandlerComponent: PromoteAsync, - HandlerRefComponent: PromoteRef, - } -} -``` - -`PromoteTryComputer` starts from a provider that implements `TryComputer`. It routes `TryComputerComponent` to `TryPromote` and defers all the remaining components to `PromoteComputer`, so the fallible base is first turned into a plain computer and the rest of the family is derived from there. - -`PromoteProducer` starts from a `Producer` — a provider that takes no input. It routes `ComputerComponent` to `Promote` (which discards the computer's input and calls `produce`) and defers the rest to `PromoteComputer`, so a single produced value flows out of every handler shape regardless of input. - -`PromoteAsyncComputer` starts from a provider that implements `AsyncComputer`. It wires `HandlerComponent` to `Promote` (wrap the awaited value in `Ok`) and the `AsyncComputerRefComponent` and `HandlerRefComponent` to `PromoteRef`. It is the async-base counterpart to `PromoteComputer`. - -`PromoteHandler` starts from the most general base, a provider that implements `Handler`. It routes `HandlerComponent` to `TryPromote` and defers the async-ref components to `PromoteAsyncComputer`. - -## Dispatching on the input type with `UseInputDelegate` - -`UseInputDelegate` is a delegate-style dispatcher analogous to [`UseDelegate`](use_delegate.md), but it keys its lookup table on the handler's `Input` type rather than on the `Code` type. It is defined as a one-parameter struct holding the lookup table: - -```rust -pub struct UseInputDelegate(pub PhantomData); -``` - -Whereas `UseDelegate` dispatches on the first generic parameter of a provider trait — `Code` for the handler family — `UseInputDelegate` dispatches on the `Input` parameter, so that the provider handling a value is chosen by the type of that value. The handler component traits enable both dispatchers at once: each is declared with two `#[derive_delegate]` directives, `UseDelegate` and `UseInputDelegate`, as on the `Computer` component: - -```rust -#[cgp_component(Computer)] -#[derive_delegate(UseDelegate)] -#[derive_delegate(UseInputDelegate)] -pub trait CanCompute { - type Output; - - fn compute(&self, _code: PhantomData, input: Input) -> Self::Output; -} -``` - -For `UseInputDelegate`, the second directive makes `#[cgp_component]` generate a provider impl that looks up `Components` keyed on the `Input` type and forwards to the matching delegate: - -```rust -impl Computer - for UseInputDelegate -where - Components: DelegateComponent, - Delegate: Computer, -{ - type Output = Delegate::Output; - - fn compute(context: &Context, code: PhantomData, input: Input) -> Self::Output { - Delegate::compute(context, code, input) - } -} -``` - -The lookup key is the dispatched parameter — here `Input` — while `Code` and `Context` pass through unchanged, exactly as with `UseDelegate`. (The `#[derive_delegate]` machinery groups the dispatched parameters into a tuple, which for a single parameter collapses to the parameter type itself, so the table is keyed directly on the concrete input type.) `UseInputDelegate` is wired through a nested table inside `delegate_components!` in the same way: the outer entry routes a handler component to `UseInputDelegate`, and that inner table maps each concrete input type to the provider responsible for it. The same impl shape is generated for every member of the handler family, since each declares the `UseInputDelegate` directive. - -## Examples - -A pipeline of computers shows the composition combinators with `PipeHandlers`. Suppose `Multiply` and `Add` are `Computer` providers over `u64` that read a factor or addend from a context field, and a context carries `foo`, `bar`, and `baz`: - -```rust -use cgp::prelude::*; -use cgp::extra::handler::{CanCompute, PipeHandlers}; - -#[derive(HasField)] -pub struct MyContext { - pub foo: u64, - pub bar: u64, - pub baz: u64, -} - -delegate_components! { - MyContext { - ComputerComponent: - PipeHandlers, - Add, - Multiply, - ]>, - } -} -``` - -Wiring `ComputerComponent` to `PipeHandlers` over the three-stage list composes them into `ComposeHandlers, ComposeHandlers, Multiply<…>>>`. Computing over an input of `5` on a context with `foo = 2`, `bar = 3`, `baz = 4` first multiplies by `foo`, then adds `bar`, then multiplies by `baz`, yielding `((5 * 2) + 3) * 4`. The same list could be wired to `HandlerComponent` instead, provided every stage supports the handler shape — and stages of mismatched shapes can be reconciled inline, as in `PromoteAsync>>`, which lifts a plain `Computer` stage up to the async `Handler` shape the pipeline expects. - -The promotion bundles appear most often indirectly. A provider author writing a single `Computer` impl and wiring it with [`#[cgp_computer]`](../macros/cgp_computer.md) gets `PromoteComputer` wired across the rest of the family automatically, so the one implementation answers `try_compute`, `compute_async`, and `handle` as well. Reaching for `PromoteComputer` by hand achieves the same effect when wiring the components explicitly. - -## Related constructs - -The combinators are providers of the handler component traits: [`Computer`](../components/computer.md), [`TryComputer`](../components/try_computer.md), [`Handler`](../components/handler.md), and [`Producer`](../components/producer.md), with the conceptual overview in [handlers](../../concepts/handlers.md). The composition and promotion combinators are wired automatically by the [`#[cgp_computer]`](../macros/cgp_computer.md) and [`#[cgp_producer]`](../macros/cgp_producer.md) macros, which generate a single-trait provider and delegate the rest of the family through the promotion bundles. `UseInputDelegate` is the `Input`-keyed sibling of [`UseDelegate`](use_delegate.md), both generated by the `#[derive_delegate]` directive of [`#[cgp_component]`](../macros/cgp_component.md) and wired through nested tables with [`delegate_components!`](../macros/delegate_components.md). The promotion bundles are themselves delegation tables, so they rely on [`DelegateComponent`](../traits/delegate_component.md) and propagate dependencies through [`IsProviderFor`](../traits/is_provider_for.md) to the [check traits](../../concepts/check-traits.md). - -## Source - -- The combinators are defined in `cgp-handler` under [crates/extra/cgp-handler/src/providers/](../../../crates/extra/cgp-handler/src/providers/): `compose.rs` (`ComposeHandlers`), `pipe.rs` (`PipeHandlers` and the internal `ComposeProviders` fold), `return_input.rs` (`ReturnInput`), `promote.rs` (`Promote`), `promote_async.rs` (`PromoteAsync`), `promote_ref.rs` (`PromoteRef`), `try_promote.rs` (`TryPromote`), and `promote_all.rs` (the `PromoteComputer`, `PromoteTryComputer`, `PromoteProducer`, `PromoteAsyncComputer`, and `PromoteHandler` bundles). -- `UseInputDelegate` is defined in [crates/extra/cgp-handler/src/types.rs](../../../crates/extra/cgp-handler/src/types.rs), and its provider impls are generated from the `#[derive_delegate(UseInputDelegate)]` directive on the handler component traits in [crates/extra/cgp-handler/src/components/](../../../crates/extra/cgp-handler/src/components/). diff --git a/docs/reference/providers/monad_providers.md b/docs/reference/providers/monad_providers.md deleted file mode 100644 index 497fc613..00000000 --- a/docs/reference/providers/monad_providers.md +++ /dev/null @@ -1,105 +0,0 @@ -# Monad providers - -The monad providers turn a list of handlers and a choice of monad into a single composed handler that short-circuits on the appropriate branch: `PipeMonadic` is the pipeline builder, the `IdentMonadic` / `OkMonadic` / `ErrMonadic` markers (with their transformer forms) name the monad, and `BindOk` / `BindErr` are the per-step bind providers that implement the branching. - -## Purpose - -These providers implement [monadic handler composition](../../concepts/monadic-handlers.md) on top of the [`Computer`](../components/computer.md) family. They exist so that a sequence of handlers whose outputs carry a "continue" case and a "stop" case can be chained without manually pattern-matching each step: the monad decides which case threads forward and which short-circuits, and the providers assemble the composition in types. The result of building a pipeline is itself a provider for `Computer`, `AsyncComputer`, `TryComputer`, and `Handler`, so a monadic pipeline wires into a context exactly like any other handler provider. - -The providers divide into three groups. `PipeMonadic` is the entry point a user wires or invokes. The monad markers are the zero-sized types that select the short-circuiting behavior. The bind providers are the lower-level building blocks that `PipeMonadic` composes internally, and that can also be used directly with the non-monadic [`PipeHandlers`](../providers/handler_combinators.md) when finer control is wanted. - -## The pipeline provider - -`PipeMonadic` is the provider that composes a handler list `Providers` under a monad `M` into a single short-circuiting handler: - -```rust -pub struct PipeMonadic(pub PhantomData<(M, Providers)>); -``` - -`M` is a monad marker and `Providers` is a [type-level list](../types/cons.md) of handler providers, written with `Product![...]`. `PipeMonadic` implements the handler components by folding the list: it delegates `ComputerComponent` and `AsyncComputerComponent` to the provider that an internal `BindProviders` computation produces from the list. That fold walks the list so that the first provider runs on the input and its result is bound, via the monad, to the monadically-composed rest of the list — each step running only on the previous step's continue branch. - -`PipeMonadic` also implements `TryComputerComponent` and `HandlerComponent`, the fallible and async-fallible handler components, by a bridge through the err monad. For these it first maps every provider in the list to `TryPromote` (demoting fallible handlers to plain `Computer`s whose output is an explicit `Result`), applies `ErrMonadic` as a transformer on top of the given monad `M`, composes that demoted list under the transformed monad, and finally wraps the composed provider back in `TryPromote` to restore the fallible interface. The effect is that a `PipeMonadic` over fallible handlers short-circuits on the context's error type in addition to whatever branching `M` itself contributes. - -## Monad markers - -The monad markers are zero-sized types that select which branch of a step's output continues the pipeline and which short-circuits. CGP defines three base markers and a transformer form for the two that branch on `Result`. - -`IdentMonadic` is the identity monad: it threads every value forward and never short-circuits, so a `PipeMonadic` is equivalent to plain composition with `PipeHandlers`. - -```rust -pub struct IdentMonadic; -``` - -`OkMonadic` short-circuits on `Ok` and continues on `Err`, and `ErrMonadic` short-circuits on `Err` and continues on `Ok`: - -```rust -pub struct OkMonadic; -pub struct ErrMonadic; -``` - -`ErrMonadic` is the familiar early-return-on-error behavior, where the first `Err` produced by any step becomes the pipeline's output and the rest do not run. `OkMonadic` is its mirror, stopping at the first `Ok`. - -Each `Result`-branching marker has a transformer form, `OkMonadicTrans` and `ErrMonadicTrans`, that applies its behavior on top of a base monad `M` so monads can stack over nested result types: - -```rust -pub struct OkMonadicTrans(pub PhantomData); -pub struct ErrMonadicTrans(pub PhantomData); -``` - -Writing `OkMonadicTrans` builds a monad that short-circuits on an outer `Ok` while threading an inner `Result` through the err monad beneath it. The bare `OkMonadic` and `ErrMonadic` markers produce their own transformer forms over `IdentMonadic` when used as transformers, so a single layer of branching needs no explicit transformer. - -## Bind providers - -`BindOk` and `BindErr` are the per-step providers that implement a single bind of the ok and err monads. `PipeMonadic` composes them internally, but they are also usable directly as handler providers — for example inside a [`PipeHandlers`](../providers/handler_combinators.md) list — when a pipeline is built step by step rather than through `PipeMonadic`. - -```rust -pub struct BindOk(pub PhantomData<(M, Cont)>); -pub struct BindErr(pub PhantomData<(M, Cont)>); -``` - -In both, `M` is the monad layer beneath this bind and `Cont` is the continuation provider to run on the continue branch. `BindErr` implements `Computer` and `AsyncComputer` for an input of `Result`: on `Ok(value)` it runs `Cont` on `value` and lifts the continuation's output back through `M`, and on `Err(err)` it short-circuits by lifting the error directly to the output, skipping `Cont`. `BindOk` is the mirror, branching on `Result`: it runs `Cont` on the `Err` payload and short-circuits on `Ok`. The `M` parameter is what lets these binds nest — at the bottom of a single-layer pipeline it is `IdentMonadic`, and a stacked monad threads a deeper monad through it. - -## The `TryPromoteProviders` mapper - -`TryPromoteProviders` is the type-level mapper that `PipeMonadic` uses to demote a whole list of fallible handler providers to infallible ones in one step: - -```rust -pub struct TryPromoteProviders; - -impl MapType for TryPromoteProviders { - type Map = TryPromote; -} -``` - -It implements [`MapType`](../traits/map_type.md) by mapping each provider to `TryPromote`, so applying it to a handler list with `MapFields` rewrites every element to its `TryPromote` form. `PipeMonadic` uses this when implementing the fallible handler components, turning a list of `TryComputer` providers into a list of plain `Computer` providers whose output is an explicit `Result` before composing them under the err-transformed monad. The `TryPromote` provider it wraps each element in is documented with the [handler combinators](../providers/handler_combinators.md); it converts between the fallible and infallible handler interfaces in both directions. - -## Examples - -The simplest use composes a homogeneous list under a base monad. With an `Increment` computer that returns `Result` — `Ok` on success and `Err("overflow")` on overflow — composing three under `ErrMonadic` chains on the `Ok` value and stops at the first error: - -```rust -PipeMonadic::::compute(&context, code, 253) -// 253 -> Ok(254) -> Ok(255) -> Err("overflow") -``` - -A single bind step can be assembled by hand and run through `PipeHandlers`, which is what `PipeMonadic` does internally for a two-element list: - -```rust -PipeHandlers::]>::compute(&context, code, 1) -// 1 -> Ok(2) -> BindErr runs the second Increment on 2 -> Ok(3) -``` - -Stacking monads handles nested results. Composing handlers that return `Result, &str>` under `OkMonadicTrans` short-circuits on the outer `Ok` while threading the inner `Result` through the err monad, and the same list composed under `OkMonadic` can be driven through the fallible `try_compute` and async `handle` entry points because `PipeMonadic` implements `TryComputer` and `Handler` as well: - -```rust -PipeMonadic::::try_compute(&context, code, 1) -``` - -## Related constructs - -`PipeMonadic` generalizes the non-monadic [handler combinators](../providers/handler_combinators.md): `PipeHandlers` and `ComposeHandlers` chain handlers feeding each output straight into the next, which is exactly what `PipeMonadic` reduces to, while the `TryPromote` provider those combinators define is what `PipeMonadic` uses to bridge fallible and infallible handlers. The monads these providers consume are defined by the trait layer in [monad traits](../traits/monad.md) — `MonadicTrans`, `MonadicBind`, `ContainsValue`, and `LiftValue` — and the conceptual overview of why a monadic pipeline short-circuits is in [monadic handlers](../../concepts/monadic-handlers.md). The pipelines build providers for the [`Computer`](../components/computer.md) family, and `TryPromoteProviders` relies on [`MapType`](../traits/map_type.md) and `MapFields` to map over the handler list. For selecting one handler among several by a type-level key rather than running them in sequence, see [dispatch combinators](../providers/dispatch_combinators.md). - -## Source - -- The pipeline provider, `TryPromoteProviders`, and the internal `BindProviders` fold are in [crates/extra/cgp-monad/src/providers/pipe_monadic.rs](../../../crates/extra/cgp-monad/src/providers/pipe_monadic.rs). -- The monad markers and bind providers are in [crates/extra/cgp-monad/src/monadic/](../../../crates/extra/cgp-monad/src/monadic/) — `ident.rs` for `IdentMonadic`, `ok.rs` for `OkMonadic` / `OkMonadicTrans` / `BindOk`, and `err.rs` for `ErrMonadic` / `ErrMonadicTrans` / `BindErr`. diff --git a/docs/reference/providers/redirect_lookup.md b/docs/reference/providers/redirect_lookup.md deleted file mode 100644 index b71df410..00000000 --- a/docs/reference/providers/redirect_lookup.md +++ /dev/null @@ -1,84 +0,0 @@ -# `RedirectLookup` - -`RedirectLookup` is a zero-sized provider that implements a component's provider trait by looking up a type-level path in a separate table, re-routing the lookup along that path instead of resolving the component against the context directly. - -## Purpose - -`RedirectLookup` decouples *which key* a component is looked up under from *which table* answers it. The ordinary provider blanket impl looks a component up in the context's own [delegation table](../traits/delegate_component.md), keyed by the component-name struct. `RedirectLookup` does the lookup differently: it consults the table `Components` keyed by an arbitrary type-level `Path`, then delegates to whatever provider that entry holds. This indirection is what lets one component's resolution be redirected to a different key in a different table — the basis for organizing wiring into namespaces and presets. - -The redirection makes namespaces possible. A namespace groups a context's components under a path prefix so that several related components can be wired in one place and addressed by a shared path. `RedirectLookup` is the provider that turns a prefixed path back into a concrete provider: the namespace machinery sets a component's delegate to a `RedirectLookup` carrying the path under which the real provider was registered, so a lookup of the component follows that path into the table and lands on the intended provider. Presets are built the same way — a preset is a reusable table whose entries are reached through redirected paths. - -`RedirectLookup` is not written by hand; it is emitted by macros. Every `#[cgp_component]` generates a `RedirectLookup` impl for its provider trait, and the namespace attributes generate `DelegateComponent` entries whose delegate is a `RedirectLookup`. Reading those generated entries is where this provider appears. - -Like every CGP provider, `RedirectLookup` carries no runtime value. Both type parameters are held in `PhantomData`, and the struct exists only as a type-level marker describing a lookup to perform. - -## Definition - -`RedirectLookup` is a struct parameterized by a key and a table, defined in `cgp-component`: - -```rust -pub struct RedirectLookup(pub PhantomData<(Key, Components)>); -``` - -The `Key` parameter is the type-level path to look up — typically a [`PathCons`](../types/path_cons.md) chain of [`Symbol!`](../macros/symbol.md) segments ending in a component-name struct. The `Components` parameter is the table to look it up in, a type implementing [`DelegateComponent`](../traits/delegate_component.md). In the generated impls the two appear in the order `RedirectLookup`, with the table first and the path second. The `PhantomData` makes both parameters part of a valueless struct. - -## Behavior - -`#[cgp_component]` generates a `RedirectLookup` impl of the provider trait alongside the consumer blanket impl, the provider blanket impl, the component-name struct, and the [`UseContext`](use_context.md) impl. The generated impl looks the path up in the table and forwards to the resulting delegate. For a component such as - -```rust -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self) -> String; -} -``` - -the macro generates this impl (shown with the macro's real placeholder identifiers): - -```rust -impl<__Context__, __Components__, __Path__> Greeter<__Context__> - for RedirectLookup<__Components__, __Path__> -where - __Components__: DelegateComponent<__Path__>, - <__Components__ as DelegateComponent<__Path__>>::Delegate: Greeter<__Context__>, -{ - fn greet(__context__: &__Context__) -> String { - <__Components__ as DelegateComponent<__Path__>>::Delegate::greet(__context__) - } -} -``` - -The mechanism is one `DelegateComponent` lookup keyed on `__Path__` rather than on the component name. `RedirectLookup` implements `Greeter` whenever `Components` maps `Path` to a delegate that itself implements `Greeter`, and the method forwards to that delegate. When the consumer trait carries generic type parameters, the impl additionally constrains `Path` with [`ConcatPath`](../traits/static_format.md) so the parameters are appended to the path before the lookup, letting the redirected key encode the generic arguments. As always, the impl is paired with a matching `IsProviderFor` impl so dependencies reach the [check traits](../../concepts/check-traits.md). - -The namespace attributes are what populate the path side. The `#[prefix(@path in Namespace)]` attribute on a component generates a namespace impl whose `Delegate` is `RedirectLookup`, with the prefix path joined onto the component name — so resolving the component under that namespace follows the prefixed path into the table. The `DefaultNamespace` trait plays the same role for the default routing. Together these turn a path-addressed wiring entry into a concrete provider through `RedirectLookup`. - -## Examples - -`RedirectLookup` appears in the delegate that the namespace machinery generates, where a component is registered under a path and reached through that path. Wiring a component under a path prefix produces a `RedirectLookup` entry: - -```rust -use cgp::prelude::*; - -pub struct App; - -delegate_components! { - App { - namespace DefaultNamespace; - - @bar.baz: TestProvider, - } -} -``` - -This registers `TestProvider` under the path `bar`/`baz` in `App`'s default namespace. When a component is later resolved against `App` through that namespace, its delegate is a `RedirectLookup` whose `Path` is the `PathCons` chain `bar` then `baz` then the component name. The lookup follows that path into `App`'s table — matching the entry registered above — and dispatches to `TestProvider`. The component name never keys the context directly; it is the tail of a path that `RedirectLookup` walks. This is the indirection that lets namespaces and presets organize wiring by path while still resolving to ordinary providers. - -## Related constructs - -`RedirectLookup` is generated by [`#[cgp_component]`](../macros/cgp_component.md) for every component, and is central to the namespace and preset machinery driven by [`#[cgp_namespace]`](../macros/cgp_namespace.md) and explained in [namespaces](../../concepts/namespaces.md). Its lookup is a [`DelegateComponent`](../traits/delegate_component.md) read keyed on a type-level path built from [`PathCons`](../types/path_cons.md) and [`Symbol!`](../macros/symbol.md), with generic parameters folded in through [`ConcatPath`](../traits/static_format.md). It sits beside the other `#[cgp_component]`-generated provider [`UseContext`](use_context.md), which routes back to the context rather than through a separate table, and its dependency propagation flows through [`IsProviderFor`](../traits/is_provider_for.md) for the [check traits](../../concepts/check-traits.md). - -## Source - -- The struct is defined in [crates/core/cgp-component/src/providers/redirect_lookup.rs](../../../crates/core/cgp-component/src/providers/redirect_lookup.rs), and the related `DefaultNamespace` trait in [crates/core/cgp-component/src/namespaces.rs](../../../crates/core/cgp-component/src/namespaces.rs). -- The `RedirectLookup` provider impl is generated by `to_redirect_lookup_impl` in [crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_redirect_lookup_impl.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_redirect_lookup_impl.rs), which appends generic parameters through `ConcatPath`. -- The namespace delegates that target `RedirectLookup` are produced by the `#[prefix]` attribute in [crates/macros/cgp-macro-core/src/types/attributes/prefix.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/prefix.rs) and the redirect mapping in [crates/macros/cgp-macro-core/src/types/delegate_component/mapping/redirect.rs](../../../crates/macros/cgp-macro-core/src/types/delegate_component/mapping/redirect.rs). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_component](../../implementation/entrypoints/cgp_component.md). diff --git a/docs/reference/providers/use_context.md b/docs/reference/providers/use_context.md deleted file mode 100644 index e0aa0541..00000000 --- a/docs/reference/providers/use_context.md +++ /dev/null @@ -1,90 +0,0 @@ -# `UseContext` - -`UseContext` is a zero-sized provider that implements any CGP provider trait by forwarding its methods back to the context's own consumer-trait implementation. - -## Purpose - -`UseContext` exists to turn a context's existing consumer-trait implementation into a provider that other providers can call. A provider trait is normally implemented by some dedicated provider type, but sometimes the implementation you want is exactly the one the context already supplies through its consumer trait. `UseContext` is that bridge: it is a provider whose method bodies simply call the consumer method on the context, so wiring a component to `UseContext` means "use whatever this context already does for this trait." - -This makes `UseContext` the exact dual of the consumer-trait blanket implementation. The blanket impl of a consumer trait such as `CanGreet` runs in the consumer-to-provider direction — a context implements `CanGreet` by delegating to whichever provider implements `Greeter` for it. `UseContext` runs in the opposite direction: it implements the provider trait `Greeter` by delegating to whatever `CanGreet` implementation the context has. One forwards the consumer trait to a provider; the other forwards a provider trait back to the consumer trait. - -The pattern matters most for [higher-order providers](../../concepts/higher-order-providers.md), which take another provider as a type parameter. A higher-order provider needs an inner provider to delegate to, and `UseContext` lets that inner provider be "the context's own wiring." When a higher-order provider defaults its inner-provider parameter to `UseContext`, the provider falls back to whatever the main context already wires for that component, rather than forcing the author to name an explicit inner provider. - -Like every CGP provider, `UseContext` carries no runtime value. It is a unit struct used purely as a type-level marker; the `self` position of its provider impls is never read, and there is nothing to construct or store. - -## Definition - -`UseContext` is a unit struct with no fields, defined in `cgp-component`: - -```rust -pub struct UseContext; -``` - -Its only state is its identity as a type. Every implementation `UseContext` carries is generated for it by `#[cgp_component]`, not written by hand, so the struct definition itself is deliberately empty. - -## Behavior - -`#[cgp_component]` emits a `UseContext` implementation of the provider trait for every component it defines, alongside the consumer blanket impl, the provider blanket impl, the component-name struct, and the [`RedirectLookup`](redirect_lookup.md) impl. The generated impl constrains the context to implement the consumer trait and then forwards each method to it. For a component such as - -```rust -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} -``` - -the macro generates the following `UseContext` impl (shown with the macro's real placeholder identifiers): - -```rust -impl<__Context__> Greeter<__Context__> for UseContext -where - __Context__: CanGreet, -{ - fn greet(__context__: &__Context__) { - __Context__::greet(__context__) - } -} -``` - -The provider method `greet` takes the context explicitly and calls the context's own `CanGreet::greet`. Each `UseContext` impl is paired with a matching `IsProviderFor` impl carrying the same `where` clause, so that delegation propagates the dependency and the [check traits](../../concepts/check-traits.md) can report a missing consumer-trait implementation precisely. Supertrait bounds on the consumer trait are reproduced in the `UseContext` impl's `where` clause, so a context must satisfy them before `UseContext` can stand in as a provider. - -The cyclic-delegation caveat is the one rule to respect: a context must never delegate a component to `UseContext`. Doing so asks the context to implement the consumer trait by delegating to a provider (`UseContext`) that in turn implements the provider trait by calling the consumer trait — a cycle the trait solver cannot resolve, surfacing as an overflow or unsatisfied-bound error. `UseContext` is meant to be supplied to *another* provider as its inner provider, not wired as a context's own delegate for the same component. - -## Examples - -The idiomatic use of `UseContext` is as the default inner provider of a higher-order provider, so that the higher-order provider routes through the context's existing wiring unless told otherwise. Consider an area calculator that scales the result of an inner calculator: - -```rust -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -pub struct ScaledArea(pub PhantomData); - -#[cgp_impl(ScaledArea)] -impl AreaCalculator -where - InnerCalculator: AreaCalculator, -{ - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - InnerCalculator::area(self) * scale_factor * scale_factor - } -} -``` - -Because `ScaledArea` defaults `InnerCalculator` to `UseContext`, wiring a context to `ScaledArea` (with no explicit inner provider) makes the inner `area` call resolve to the context's own `CanCalculateArea` implementation. The context computes a base area through whatever `AreaCalculator` it already wires, and `ScaledArea` multiplies that by the scale factor. Overriding the parameter — for example `ScaledArea` — instead binds the inner calculation statically to `RectangleArea`, bypassing the context's wiring for that step. - -Note that `UseContext` only acts as a default when a higher-order provider's struct definition gives it as the default generic parameter, as `ScaledArea` does above. A provider without such a default has no inner provider to fall back to, and the inner provider must always be named explicitly. - -## Related constructs - -`UseContext` is the dual of the consumer-trait blanket impl that [`#[cgp_component]`](../macros/cgp_component.md) generates, and the macro emits both for every component. It is most useful with [higher-order providers](../../concepts/higher-order-providers.md) as their default inner provider, and the [dispatch combinators](dispatch_combinators.md) use it the same way — `MatchWithValueHandlers` defaults its per-variant provider to `UseContext`, so each matched payload routes back through the context's own wiring and any one variant's handler can be overridden in the table without disturbing the others, as the [extensible shapes](../../examples/extensible-shapes.md) example shows. It also composes with [`WithProvider`](with_provider.md) through the alias `WithContext = WithProvider`, which adapts a context's foundational getter or type implementation into a component. The closely related [`RedirectLookup`](redirect_lookup.md) provider, also generated by `#[cgp_component]`, routes a lookup through a separate table rather than back to the context. The dependency propagation that keeps `UseContext` honest in [check traits](../../concepts/check-traits.md) flows through [`IsProviderFor`](../traits/is_provider_for.md). - -## Source - -- The struct is defined in [crates/core/cgp-component/src/providers/use_context.rs](../../../crates/core/cgp-component/src/providers/use_context.rs), which also declares the `WithContext` alias. -- The `UseContext` provider impl is generated by `to_use_context_impl` in [crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_use_context_impl.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/to_use_context_impl.rs), which forwards each provider-trait method to the consumer trait via `trait_items_to_delegated_impl_items`. -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_component](../../implementation/entrypoints/cgp_component.md). diff --git a/docs/reference/providers/use_default.md b/docs/reference/providers/use_default.md deleted file mode 100644 index fb50ed1d..00000000 --- a/docs/reference/providers/use_default.md +++ /dev/null @@ -1,111 +0,0 @@ -# `UseDefault` - -`UseDefault` is a zero-sized marker provider that a component is wired to when its implementation should come entirely from the consumer trait's default method bodies. - -## Purpose - -`UseDefault` names the "use the trait's own defaults" choice in CGP wiring. A consumer trait may define default bodies for its methods, the same way any Rust trait can. When every method of a component has a usable default, there is no behavior left for a provider to supply — the provider only needs to exist so the component can be wired. `UseDefault` is that empty provider: wiring a component to `UseDefault` selects an implementation whose method bodies are inherited from the trait's defaults rather than supplied by a dedicated provider. - -This keeps a default-only component consistent with the rest of CGP wiring. Without it, a component whose methods are all defaulted would still need some provider type and some `delegate_components!` entry to participate in the delegation table; `UseDefault` is the shared name for that role, so authors do not invent a one-off marker each time. A context that wants the defaults wires the component to `UseDefault` and writes no method bodies of its own. - -`UseDefault` is a bare marker that CGP defines but does not implement for any trait. Unlike [`UseContext`](use_context.md), [`UseFields`](use_fields.md), or [`UseField`](use_field.md), no macro generates a provider impl for it; the provider impl is written by the author, typically with [`#[cgp_impl]`](../macros/cgp_impl.md) and an empty body so the trait's defaults take effect. This is what distinguishes `UseDefault` from the providers that carry generated behavior: it is purely a conventional name for an author-supplied, default-bodied implementation. - -Like every CGP provider, `UseDefault` carries no runtime value. It is a unit struct used purely as a type-level marker, and its provider impls never read the `self` position. - -## Definition - -`UseDefault` is a unit struct with no fields, defined in `cgp-component`: - -```rust -pub struct UseDefault; -``` - -It is exported from `cgp-component` but is not part of the `cgp::prelude`, so reaching it requires naming it through `cgp_component::UseDefault`. The struct carries no behavior of its own; meaning is given to it by the provider impl an author writes against it. - -## Behavior - -A component is wired to `UseDefault` by implementing the provider trait for it with empty method bodies, which causes the consumer trait's default bodies to be used. The implementing author leaves each method body out, so the default defined on the consumer trait fills in. Consider a getter and a greeter whose methods both have defaults: - -```rust -#[cgp_getter] -pub trait HasName { - fn name(&self) -> &str { - "John" - } -} - -#[cgp_component(Greeter)] -pub trait CanGreet: HasName { - fn greet(&self) -> String { - format!("Hello, {}!", self.name()) - } -} -``` - -Provider impls against `UseDefault` are written with empty bodies, so the defaults take over: - -```rust -#[cgp_impl(UseDefault)] -impl NameGetter {} - -#[cgp_impl(UseDefault)] -#[uses(HasName)] -impl Greeter {} -``` - -The first impl makes `UseDefault` a `NameGetter` provider whose `name` is the trait default `"John"`; the second makes it a `Greeter` provider whose `greet` is the trait default that formats around `self.name()`. The `Greeter` impl restates the consumer-side dependency `HasName` with [`#[uses(HasName)]`](../attributes/uses.md), because the default body of `greet` calls `name`. Each `#[cgp_impl]` also generates the matching [`IsProviderFor`](../traits/is_provider_for.md) impl, so the dependency propagates to the [check traits](../../concepts/check-traits.md) exactly as for any other provider. - -Because `UseDefault` has no generated impls, the author controls precisely which components it serves. A type that has not had a provider impl written for a given component is simply not a provider for it; there is no automatic fallback, and the empty-body `#[cgp_impl]` is the explicit opt-in. - -## Examples - -The use of `UseDefault` is to wire several default-bodied components to one shared marker, then delegate them all to it in the context's table. Building on the traits above: - -```rust -use cgp::prelude::*; -use cgp_component::UseDefault; - -#[cgp_getter] -pub trait HasName { - fn name(&self) -> &str { - "John" - } -} - -#[cgp_component(Greeter)] -pub trait CanGreet: HasName { - fn greet(&self) -> String { - format!("Hello, {}!", self.name()) - } -} - -#[cgp_impl(UseDefault)] -impl NameGetter {} - -#[cgp_impl(UseDefault)] -#[uses(HasName)] -impl Greeter {} - -pub struct App; - -delegate_components! { - App { - [ - NameGetterComponent, - GreeterComponent, - ]: - UseDefault, - } -} -``` - -`App` delegates both `NameGetterComponent` and `GreeterComponent` to `UseDefault`, so `App.greet()` produces `"Hello, John!"` entirely from the two default bodies — no method is implemented on `App` or on a dedicated provider. The array syntax routes both components to the single `UseDefault` marker in one entry. - -## Related constructs - -`UseDefault` is wired with [`delegate_components!`](../macros/delegate_components.md) and its provider impls are written with [`#[cgp_impl]`](../macros/cgp_impl.md), distinguishing it from [`UseContext`](use_context.md) and the getter providers [`UseFields`](use_fields.md) and [`UseField`](use_field.md), which carry macro-generated behavior rather than relying on author-written empty bodies. Its dependency tracking flows through [`IsProviderFor`](../traits/is_provider_for.md) and is checked with [`check_components!`](../macros/check_components.md), the same as any other provider. - -## Source - -- The struct is defined in [crates/core/cgp-component/src/providers/use_default.rs](../../../crates/core/cgp-component/src/providers/use_default.rs) and re-exported in [crates/core/cgp-component/src/providers/mod.rs](../../../crates/core/cgp-component/src/providers/mod.rs); the file contains only the bare struct, with no macro-generated impls. -- For how it is generated and the index of tests, see the implementation document [implementation/asts/attributes/default_impl.md](../../implementation/asts/attributes/default_impl.md). diff --git a/docs/reference/providers/use_delegate.md b/docs/reference/providers/use_delegate.md deleted file mode 100644 index a5b2e46f..00000000 --- a/docs/reference/providers/use_delegate.md +++ /dev/null @@ -1,101 +0,0 @@ -# `UseDelegate` - -`UseDelegate` is a zero-sized provider that implements a generic-parameterized provider trait by using `Components` as a type-level lookup table, dispatching to a different inner provider for each value of a chosen generic parameter. - -> **Legacy:** `UseDelegate`, the [`#[derive_delegate]`](../attributes/derive_delegate.md) attribute that generates its provider impl, and the [`UseDelegate` nested-table wiring](../macros/delegate_components.md) are a legacy dispatch mechanism. The same per-type dispatch is now expressed more ergonomically with the `open` statement of [`delegate_components!`](../macros/delegate_components.md), which folds the per-value entries directly into the context's own table — no separate table type, no `UseDelegate` wrapper, and no `#[derive_delegate]` on the component, since the dispatch rides the [`RedirectLookup`](redirect_lookup.md) impl every [`#[cgp_component]`](../macros/cgp_component.md) already generates. Prefer `open` for new code; `UseDelegate` is retained for compatibility and is expected to be deprecated, and eventually removed, once the namespace-based form is shown to cover every dispatch case. The rest of this document describes the legacy mechanism as it stands today. - -## Purpose - -`UseDelegate` solves the problem of choosing a provider based on a type argument rather than on the component alone. An ordinary CGP component picks its provider by looking up the component name in the context's [delegation table](../traits/delegate_component.md). But when a provider trait carries extra generic parameters — a `SourceError` to convert, a `Shape` to measure, an `Input` to compute over — the right provider often depends on which concrete type that parameter is. `UseDelegate` is the provider that performs this second lookup: it treats one of the generic parameters as a key and reads the matching inner provider out of a table, so a single wiring entry can fan out to many type-specific providers. - -This lets context-generic providers stay generic even when their implementations would otherwise overlap on a parameter. Instead of writing one provider that matches on every possible `Shape`, you write a small provider per shape and let `UseDelegate` route each concrete shape to its own. The dispatch is ad hoc and type-directed: the key is a real type appearing in the trait, and the table maps that type to the provider responsible for it. - -`UseDelegate` follows the same delegation pattern as the provider blanket impl, but with a different key. Where the blanket impl uses the component-name struct as the key into the *context's* table, `UseDelegate` uses the chosen generic parameter as the key into its *own* `Components` table. It is the default dispatcher CGP offers for this purpose, so authors need not invent a fresh dispatcher type for the common case, though defining additional dispatcher types is supported when several parameters must be routed independently. - -Like every CGP provider, `UseDelegate` holds no runtime value. The `Components` type parameter is carried in `PhantomData`, and the struct exists only as a type-level marker that names the lookup table to use. - -## Definition - -`UseDelegate` is a unit-like struct parameterized by the table type, defined in `cgp-component`: - -```rust -pub struct UseDelegate(pub PhantomData); -``` - -The single type parameter `Components` is the type-level lookup table — a type that implements [`DelegateComponent`](../traits/delegate_component.md) keyed on the generic parameter being dispatched. The `PhantomData` exists only to make `Components` a parameter of a struct that has no values; nothing of `Components` is ever constructed. - -## Behavior - -The provider impl for `UseDelegate` is generated by the `#[derive_delegate]` attribute of [`#[cgp_component]`](../macros/cgp_component.md), which names the generic parameter to dispatch on. Given a component whose trait has an extra parameter and a `derive_delegate` directive, - -```rust -#[cgp_component { - provider: ErrorRaiser, - derive_delegate: UseDelegate, -}] -pub trait CanRaiseError: HasErrorType { - fn raise_error(error: SourceError) -> Self::Error; -} -``` - -the macro generates an `ErrorRaiser` impl for `UseDelegate` that uses `Components` as the table and `SourceError` as the key: - -```rust -impl ErrorRaiser - for UseDelegate -where - Context: HasErrorType, - Components: DelegateComponent, - Delegate: ErrorRaiser, -{ - fn raise_error(error: SourceError) -> Context::Error { - Delegate::raise_error(error) - } -} -``` - -The mechanism is a single `DelegateComponent` lookup keyed on `SourceError`. `UseDelegate` implements `ErrorRaiser` for a given `SourceError` exactly when `Components` maps that `SourceError` to a `Delegate` that itself implements `ErrorRaiser` for that `SourceError`, and the method body forwards to that delegate. Only the parameter named inside `UseDelegate<...>` is used as the key — here `SourceError` alone — so the dispatch keys on one parameter while the rest pass through unchanged. As elsewhere in CGP, each provider impl is paired with an `IsProviderFor` impl so dependencies propagate to the [check traits](../../concepts/check-traits.md). - -A component may derive more than one dispatcher when different parameters should be routed differently. Passing a list to `derive_delegate` generates one impl per dispatcher — the default `UseDelegate` keying on the first parameter, plus any custom dispatcher types keying on the others — so each parameter can be looked up through its own table. - -## Examples - -`UseDelegate` is most often wired through a nested table inside `delegate_components!`, which builds both the outer entry and the inner lookup table in one place. Given an area component that dispatches on a `Shape` parameter: - -```rust -use cgp::prelude::*; - -#[cgp_component { - provider: AreaCalculator, - derive_delegate: UseDelegate, -}] -pub trait CanCalculateArea { - fn area(&self, shape: &Shape) -> f64; -} - -pub struct Rectangle; -pub struct Circle; - -delegate_components! { - MyApp { - AreaCalculatorComponent: - UseDelegate, - } -} -``` - -The wiring reads in two layers. `MyApp` delegates `AreaCalculatorComponent` to `UseDelegate`, so `MyApp`'s area calculation routes through `UseDelegate` using `AreaCalculatorComponents` as the table. That inner table — defined inline by the `new` keyword — maps the `Rectangle` type to the `RectangleArea` provider and the `Circle` type to `CircleArea`. The result is that `MyApp` implements `CanCalculateArea` through `RectangleArea` and `CanCalculateArea` through `CircleArea`, with `UseDelegate` selecting between them based on the `Shape` argument. Adding a new shape is one more entry in the inner table; the providers themselves stay untouched. - -## Related constructs - -`UseDelegate`'s provider impl is generated by the [`#[derive_delegate]`](../attributes/derive_delegate.md) attribute of [`#[cgp_component]`](../macros/cgp_component.md), and it is wired — usually through a nested table — with [`delegate_components!`](../macros/delegate_components.md). The lookup itself is a [`DelegateComponent`](../traits/delegate_component.md) read keyed on a generic parameter rather than on a component name, which is the same trait the ordinary provider blanket impl uses with the component name as key. Dependency propagation for the dispatched provider flows through [`IsProviderFor`](../traits/is_provider_for.md), letting [check traits](../../concepts/check-traits.md) report which type-specific provider is missing a dependency. - -## Source - -- The struct is defined in [crates/core/cgp-component/src/providers/use_delegate.rs](../../../crates/core/cgp-component/src/providers/use_delegate.rs). -- The `UseDelegate` provider impl is generated from the `#[derive_delegate]` directive parsed in [crates/macros/cgp-macro-core/src/types/attributes/](../../../crates/macros/cgp-macro-core/src/types/attributes/) and emitted by the `#[cgp_component]` pipeline in [crates/macros/cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/). -- The nested-table wiring is handled by `delegate_components!` in [crates/macros/cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/). -- For how it is generated and the index of tests, see the implementation document [implementation/asts/attributes/derive_delegate.md](../../implementation/asts/attributes/derive_delegate.md). diff --git a/docs/reference/providers/use_delegated_type.md b/docs/reference/providers/use_delegated_type.md deleted file mode 100644 index 60e66032..00000000 --- a/docs/reference/providers/use_delegated_type.md +++ /dev/null @@ -1,92 +0,0 @@ -# `UseDelegatedType` - -`UseDelegatedType` is a zero-sized type provider that resolves an abstract CGP type by looking the type tag up in a delegation table, rather than fixing it to a single concrete type. - -## Purpose - -`UseDelegatedType` exists for the case where the concrete type an abstract type resolves to should itself be decided by a type-level table. The plain [`UseType`](use_type.md) provider binds an abstract type to one fixed `T`. But sometimes a single provider must answer several abstract-type components at once, or route each type tag to a different concrete type chosen elsewhere — for instance when a preset or a higher-order provider supplies a coherent bundle of types. Hand-writing one `UseType` wiring per tag would scatter that decision; `UseDelegatedType` concentrates it into one `Components` table that the provider consults. - -The mechanism is the same indirection that [`UseDelegate`](use_delegate.md) provides for behavioral components, lifted to the type level. Where `UseDelegate` dispatches a *method call* to whichever provider `Components` maps the active tag to, `UseDelegatedType` dispatches a *type resolution* to whichever concrete type `Components` maps the active tag to. It is the type-level analogue of `UseDelegate`: both read an entry out of a [`DelegateComponent`](../traits/delegate_component.md) table keyed by the tag, but one yields behavior and the other yields a type. - -Like every CGP provider, `UseDelegatedType` carries no runtime value — it is a `PhantomData`-only marker named in wiring, never constructed. - -## Definition - -`UseDelegatedType` is a phantom-typed struct parameterized by the lookup table it consults, defined in `cgp-type`: - -```rust -pub struct UseDelegatedType(pub PhantomData); - -pub type WithDelegatedType = WithProvider>; -``` - -The `Components` parameter is a type that implements [`DelegateComponent`](../traits/delegate_component.md) for each type tag the provider must answer — the same kind of type-level key-value map that `delegate_components!` builds. The `WithDelegatedType` alias wraps the provider in [`WithProvider`](with_provider.md), so a user-defined [`#[cgp_type]`](../macros/cgp_type.md) component (whose generated `WithProvider` impl forwards to any `TypeProvider`) can be backed by a delegated lookup as well as the built-in [`HasType`](../components/has_type.md) component. Neither `UseDelegatedType` nor `WithDelegatedType` is re-exported through `cgp::prelude`; reach them through `cgp::core::types`. - -## Behavior - -`UseDelegatedType` implements [`TypeProvider`](../components/has_type.md) by looking the type tag `Tag` up in `Components` and reporting the delegate it finds as the abstract type: - -```rust -#[cgp_provider(TypeProviderComponent)] -impl TypeProvider for UseDelegatedType -where - Components: DelegateComponent, -{ - type Type = Type; -} -``` - -The `where` clause is the whole of the behavior: `Components: DelegateComponent` reads the entry stored at key `Tag` in the `Components` table, and the impl sets the abstract `Type` to that delegate. Because the lookup is keyed by `Tag`, one `UseDelegatedType` provider answers as many distinct type tags as `Components` has entries, each resolving to its own concrete type. If `Components` has no entry for a given tag, the `DelegateComponent` bound is unsatisfied and the context simply does not implement `HasType` for that tag — the missing-entry diagnostic from `DelegateComponent` surfaces the gap. - -Contrast this with `UseType`, whose `TypeProvider` impl is unconditional and always reports the single type `T`. `UseDelegatedType` adds exactly one level of indirection — the `DelegateComponent` lookup — so the concrete type comes from the table instead of from the provider's own parameter. - -## Examples - -A typical use defines a lookup table mapping type tags to concrete types and wires a context's type component to `UseDelegatedType` over that table. The table is an ordinary type carrying `DelegateComponent` entries: - -```rust -use cgp::prelude::*; -use cgp::core::types::UseDelegatedType; // not re-exported through the prelude - -#[cgp_type] -pub trait HasScalarType { - type Scalar; -} - -#[cgp_type] -pub trait HasIndexType { - type Index; -} - -pub struct App; -pub struct AppTypes; - -delegate_components! { - AppTypes { - ScalarTypeProviderComponent: f64, - IndexTypeProviderComponent: usize, - } -} - -delegate_components! { - App { - [ - ScalarTypeProviderComponent, - IndexTypeProviderComponent, - ]: UseDelegatedType, - } -} -``` - -`App` routes both its scalar and index type components through `UseDelegatedType`. When the wiring asks for `App`'s `Scalar`, the provider looks `ScalarTypeProviderComponent` up in `AppTypes` and finds `f64`; for `Index` it finds `usize`. A single provider entry on `App` thus answers two abstract types, with the concrete choices held in one place in `AppTypes`. - -This is what makes `UseDelegatedType` valuable for bundling: the set of concrete types lives in the `AppTypes` table and can be reused, swapped, or supplied by a preset, while each context only points its type components at the table. - -## Related constructs - -`UseDelegatedType` is the type-level counterpart of [`UseDelegate`](use_delegate.md), which performs the same `DelegateComponent` lookup for behavioral (method) components. It resolves through the [`DelegateComponent`](../traits/delegate_component.md) trait, the type-level key-value map that `delegate_components!` populates, and implements the [`HasType` / `TypeProvider`](../components/has_type.md) component it answers for. Its sibling [`UseType`](use_type.md) is the simpler provider that fixes an abstract type to one concrete type without a lookup. Its `WithDelegatedType` alias is one of the named wrappers around [`WithProvider`](with_provider.md), used to back a [`#[cgp_type]`](../macros/cgp_type.md) component with a delegated lookup. - -## Source - -- The `UseDelegatedType` struct, its `WithDelegatedType` alias, and the `TypeProvider` impl are in [crates/core/cgp-type/src/impls/use_delegated_type.rs](../../../crates/core/cgp-type/src/impls/use_delegated_type.rs). -- The `HasType` consumer trait and `TypeProvider` provider trait are in [crates/core/cgp-type/src/traits/has_type.rs](../../../crates/core/cgp-type/src/traits/has_type.rs), and `DelegateComponent` is in [crates/core/cgp-component/src/traits/delegate_component.rs](../../../crates/core/cgp-component/src/traits/delegate_component.rs). diff --git a/docs/reference/providers/use_field.md b/docs/reference/providers/use_field.md deleted file mode 100644 index 80467156..00000000 --- a/docs/reference/providers/use_field.md +++ /dev/null @@ -1,97 +0,0 @@ -# `UseField` - -`UseField` is a zero-sized provider that implements a getter component by reading a field named by `Tag` from the context through [`HasField`](../traits/has_field.md), letting the field name differ from the getter method name. - -## Purpose - -`UseField` exists to decouple a getter's method name from the field it reads. A getter component defined with [`#[cgp_getter]`](../macros/cgp_getter.md) describes a value the context can supply — `fn name(&self) -> &str` — but the context may store that value under a different field, say `first_name`, or different contexts may store it under different names. `UseField` carries the field name as its type parameter, so wiring a context's getter component to `UseField` makes the getter read `first_name` even though the method is `name`. The field name lives in the wiring, not in the trait. - -This is the provider that [`#[cgp_getter]`](../macros/cgp_getter.md) targets. When a getter trait has a single method, `#[cgp_getter]` generates a `UseField` impl for the getter's provider trait with the field tag left as a free parameter, so a context picks the field by writing `UseField` in its delegation table. `UseField` itself is the general-purpose provider underneath that pattern: it works for any tag the context's [`HasField`](../traits/has_field.md) impl supports. - -The `Tag` is usually a type-level string built with [`Symbol!`](../macros/symbol.md), such as `Symbol!("name")`, or a type-level integer wrapped in `Index` for tuple fields — these are exactly the tags that [`#[derive(HasField)]`](../derives/derive_has_field.md) generates `HasField` impls for. Any other type works as a tag too, but then the context must supply the matching `HasField` impl itself. As with every CGP provider, `UseField` carries no runtime value; it is a `PhantomData`-only marker named in wiring. - -## Definition - -`UseField` is a phantom-typed struct parameterized by the field tag, defined in `cgp-field`: - -```rust -pub struct UseField(pub PhantomData); - -pub type WithField = WithProvider>; -``` - -The `WithField` alias wraps `UseField` in [`WithProvider`](with_provider.md), the adapter that lets a generic field getter back a specific getter component. The `#[cgp_getter]` macro generates a `WithProvider` impl for its component, so a getter can be wired with either `UseField` (through the macro's own generated `UseField` impl) or `WithField` (through `WithProvider`). - -## Implementations - -`UseField` implements three provider traits, each forwarding to the context's [`HasField`](../traits/has_field.md) impl for `Tag`. The central one is the provider-side getter, [`FieldGetter`](../traits/has_field.md), which reads the field by reference: - -```rust -impl FieldGetter for UseField -where - Context: HasField, -{ - type Value = Value; - - fn get_field(context: &Context, _tag: PhantomData) -> &Value { - context.get_field(PhantomData) - } -} -``` - -Two tags appear here for a reason. `OutTag` is the tag the *component* asks under (the getter component's name), while `Tag` is the *field* tag the provider was parameterized with. The impl ignores `OutTag` entirely and reads `Tag` from the context, which is precisely the decoupling: the component's identity and the field name are independent. The associated `Value` is taken from the context's `HasField` impl, so the returned reference is to the real field. - -`UseField` also implements the mutable getter [`MutFieldGetter`](../traits/has_field.md) the same way, requiring `Context: HasFieldMut` and returning `&mut Value` via `get_field_mut`. And it implements [`TypeProvider`](../components/has_type.md), reporting the field's `Value` type as an abstract type — so the *type* of a field can itself be wired as a context's abstract type. Each impl is paired with an `IsProviderFor` impl carrying the same `HasField` bound, so delegation propagates the dependency and check traits report a missing field precisely. - -## Examples - -The defining use wires a getter to a field whose name differs from the method, which is the case the simpler [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md) cannot express. The trait method is `name`, but the context stores the value in `first_name`: - -```rust -use cgp::prelude::*; - -#[cgp_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[derive(HasField)] -pub struct Person { - pub first_name: String, -} - -delegate_components! { - Person { - NameGetterComponent: UseField, - } -} - -fn greet(person: &Person) { - println!("Hello, {}!", person.name()); // reads the first_name field -} -``` - -`Person` wires `NameGetterComponent` to `UseField`. The generated getter resolves through the `FieldGetter` impl above, whose `Tag` is `Symbol!("first_name")`, so `person.name()` reads `Person`'s `first_name` field — the method name and the field name diverge, with the field name supplied entirely by the wiring. - -The same binding can be written with the `WithField` alias, which routes through `WithProvider`: - -```rust -delegate_components! { - Person { - NameGetterComponent: WithField, - } -} -``` - -Both forms read `first_name`; `UseField` is the idiomatic choice for binding a getter to a named field without hand-writing a provider. - -## Related constructs - -`UseField` is the provider that [`#[cgp_getter]`](../macros/cgp_getter.md) generates an impl for, the mechanism that lets a getter's field name differ from its method name. It implements the provider-side [`FieldGetter` / `MutFieldGetter`](../traits/has_field.md) traits by reading the consumer-side [`HasField`](../traits/has_field.md) impl that [`#[derive(HasField)]`](../derives/derive_has_field.md) produces, keyed by tags built with [`Symbol!`](../macros/symbol.md) or `Index`. Its `WithField` alias is one of the named wrappers around [`WithProvider`](with_provider.md). It is the field-level analogue of the [`UseType`](use_type.md) provider that [`#[cgp_type]`](../macros/cgp_type.md) generates for abstract types. For a getter whose value is reached through `AsRef`/`AsMut` rather than read directly, see [`UseFieldRef`](use_field_ref.md); for chaining getters across nested contexts, see [`ChainGetters`](chain_getters.md). - -## Source - -- The `UseField` struct, its `WithField` alias, and the `FieldGetter`, `MutFieldGetter`, and `TypeProvider` impls are in [crates/core/cgp-field/src/impls/use_field.rs](../../../crates/core/cgp-field/src/impls/use_field.rs). -- The `HasField`, `FieldGetter`, and (in `has_field_mut.rs`) `HasFieldMut`, `MutFieldGetter` traits are in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/). -- The `#[cgp_getter]`-generated `UseField` impl is built in [crates/macros/cgp-macro-core/src/types/cgp_getter/use_field.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_getter/use_field.rs). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_getter](../../implementation/entrypoints/cgp_getter.md). diff --git a/docs/reference/providers/use_field_ref.md b/docs/reference/providers/use_field_ref.md deleted file mode 100644 index 9e58f947..00000000 --- a/docs/reference/providers/use_field_ref.md +++ /dev/null @@ -1,88 +0,0 @@ -# `UseFieldRef` - -`UseFieldRef` is a zero-sized provider that implements a getter component by reading a field named by `Tag` and then dereferencing it through `AsRef`/`AsMut`, so the getter exposes `&Value` rather than the field's own type. - -## Purpose - -`UseFieldRef` exists for getters whose return type is reached *through* a field rather than being the field itself. The plain [`UseField`](use_field.md) provider returns a reference to a field exactly as stored: a `String` field yields `&String`. But a getter often wants to expose a borrowed view — `&str` from a `String`, `&[T]` from a `Vec`, `&Path` from a `PathBuf` — where the stored type implements `AsRef` for the desired `Value`. `UseFieldRef` carries both the field tag and the target `Value`, reads the field, and calls `as_ref()` to produce the view, so the getter's signature can name the borrowed type while the context stores the owning type. - -This complements `UseField` rather than replacing it. `UseField` is the right provider when the getter returns the field's type directly; `UseFieldRef` is the right one when the getter returns a reference obtained by `AsRef` from the field. Both decouple the field name from the method name through `Tag`; `UseFieldRef` additionally decouples the *exposed type* from the *stored type* through `Value`. As with every CGP provider, `UseFieldRef` carries no runtime value — it is a `PhantomData`-only marker named in wiring. - -## Definition - -`UseFieldRef` is a phantom-typed struct parameterized by the field tag and the borrowed value type, defined in `cgp-field`: - -```rust -pub struct UseFieldRef(pub PhantomData<(Tag, Value)>); - -pub type WithFieldRef = WithProvider>; -``` - -`Tag` names the field, as in [`UseField`](use_field.md), and `Value` is the type the getter exposes — the type the stored field can be borrowed as via `AsRef`. The `WithFieldRef` alias wraps the provider in [`WithProvider`](with_provider.md), so a getter component can be backed by a ref-style field accessor through the macro-generated `WithProvider` impl. Unlike the more common [`UseField`](use_field.md), neither `UseFieldRef` nor `WithFieldRef` is re-exported through `cgp::prelude`; reach them through `cgp::core::field::impls`. - -## Implementations - -`UseFieldRef` implements the provider-side getter [`FieldGetter`](../traits/has_field.md) by reading the field at `Tag` and dereferencing it to `&Value`: - -```rust -impl FieldGetter for UseFieldRef -where - Context: HasField + 'static>, -{ - type Value = Value; - - fn get_field(context: &Context, _tag: PhantomData) -> &Value { - context.get_field(PhantomData).as_ref() - } -} -``` - -The `where` clause carries the defining constraint: the context's field at `Tag` must implement `AsRef`, so the stored type can be borrowed as the exposed type. As in `UseField`, `OutTag` is the tag the component asks under and is ignored; the field is read at `Tag`. The body reads the field and calls `as_ref()`, so a `String` field exposed with `Value = str` yields `&str`. The `'static` bound on the field type lets Rust infer the borrow's lifetime through the `AsRef` call. - -`UseFieldRef` also implements the mutable getter [`MutFieldGetter`](../traits/has_field.md), requiring the field type to implement both `AsRef` and `AsMut` and returning `&mut Value` via `as_mut()`. Unlike `UseField`, `UseFieldRef` does not implement `TypeProvider`, because its purpose is borrowed field access rather than abstract-type resolution. - -## Examples - -A typical use exposes a `&str` getter over a context that stores the name as a `String`, with the getter's signature naming the borrowed type. The component returns `&str`, while `Person` stores a `String`: - -```rust -use cgp::prelude::*; -use cgp::core::field::impls::UseFieldRef; // not re-exported through the prelude - -#[cgp_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[derive(HasField)] -pub struct Person { - pub name: String, -} - -delegate_components! { - Person { - NameGetterComponent: UseFieldRef, - } -} -``` - -`Person` wires `NameGetterComponent` to `UseFieldRef`. The `FieldGetter` impl reads the `name` field — a `String` — and, because `String: AsRef`, returns `&str` from `as_ref()`. The getter exposes the borrowed `str` view while the context owns the `String`. - -The same binding can be written with the `WithFieldRef` alias, which routes through `WithProvider`: - -```rust -delegate_components! { - Person { - NameGetterComponent: WithFieldRef, - } -} -``` - -## Related constructs - -`UseFieldRef` is the borrow-through-`AsRef` variant of [`UseField`](use_field.md): both read a field named by `Tag` and implement the provider-side [`FieldGetter` / `MutFieldGetter`](../traits/has_field.md) traits over [`HasField`](../traits/has_field.md), but `UseFieldRef` adds a `Value` parameter and exposes `&Value` via `AsRef`/`AsMut` instead of the field's own type. It reads fields produced by [`#[derive(HasField)]`](../derives/derive_has_field.md), keyed by tags from [`Symbol!`](../macros/symbol.md) or `Index`, and is wired to a getter defined with [`#[cgp_getter]`](../macros/cgp_getter.md). Its `WithFieldRef` alias is one of the named wrappers around [`WithProvider`](with_provider.md). For chaining getters across nested contexts, see [`ChainGetters`](chain_getters.md). - -## Source - -- The `UseFieldRef` struct, its `WithFieldRef` alias, and the `FieldGetter` and `MutFieldGetter` impls are in [crates/core/cgp-field/src/impls/use_ref.rs](../../../crates/core/cgp-field/src/impls/use_ref.rs). -- The `HasField` and `FieldGetter` traits are in [crates/core/cgp-field/src/traits/has_field.rs](../../../crates/core/cgp-field/src/traits/has_field.rs), and `HasFieldMut`/`MutFieldGetter` are in [crates/core/cgp-field/src/traits/has_field_mut.rs](../../../crates/core/cgp-field/src/traits/has_field_mut.rs). diff --git a/docs/reference/providers/use_fields.md b/docs/reference/providers/use_fields.md deleted file mode 100644 index e7969c06..00000000 --- a/docs/reference/providers/use_fields.md +++ /dev/null @@ -1,91 +0,0 @@ -# `UseFields` - -`UseFields` is a zero-sized provider that implements a getter component by reading each getter method's value from the context field named after the method. - -## Purpose - -`UseFields` is the provider form of the convention "the method `name` reads the field `name`." A getter component defined with [`#[cgp_getter]`](../macros/cgp_getter.md) describes one or more values the context can supply, and the most common arrangement is that each value lives in a same-named field. `UseFields` is the provider that realizes that arrangement: wiring a getter component to `UseFields` makes every getter method read the context field whose name equals the method name, looked up through [`HasField`](../traits/has_field.md) keyed by a [`Symbol!`](../macros/symbol.md). - -This is the provider analogue of the blanket impl that [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md) emits. `#[cgp_auto_getter]` produces a single blanket implementation that fires automatically for any context whose field names match the method names; there is nothing to wire. `UseFields` packages the same field-by-method-name behavior as a provider that a `#[cgp_getter]` component can be wired to, so a getter trait that participates in CGP wiring can still opt into the auto-getter convention when its fields happen to line up with its methods. - -`UseFields` is distinct from its sibling [`UseField`](use_field.md), and the single-versus-plural naming marks the difference. `UseField` keys on a tag the wiring chooses, letting one method read a field of any name; `UseFields` takes no parameter and keys every method on its own name. Reach for `UseField` when the field name must differ from the method name, and for `UseFields` when the convention holds and you simply want the auto-getter behavior inside a wired component. - -Like every CGP provider, `UseFields` carries no runtime value. It is a unit struct used purely as a type-level marker, and its provider impls never read the `self` position. - -## Definition - -`UseFields` is a unit struct with no fields, defined in `cgp-component`: - -```rust -pub struct UseFields; -``` - -It takes no type parameter — the key for every method is fixed to that method's own name — so the struct is a bare marker. The provider impl that gives it meaning is generated by `#[cgp_getter]`, not written by hand. - -## Behavior - -`#[cgp_getter]` generates a `UseFields` impl of the getter's provider trait, reading each method's value from the field whose name matches the method, keyed by a `Symbol!`. For a single-method getter such as - -```rust -#[cgp_getter] -pub trait HasFoo { - fn foo(&self) -> &str; -} -``` - -the macro emits this `UseFields` impl for the generated `FooGetter` provider trait (shown with the macro's real placeholder identifiers, and with `Symbol!("foo")` left in sugared form): - -```rust -impl<__Context__> FooGetter<__Context__> for UseFields -where - __Context__: HasField, -{ - fn foo(__context__: &__Context__) -> &str { - __context__.get_field(PhantomData::).as_str() - } -} -``` - -Each method becomes a `HasField` bound keyed on the method name as a `Symbol!`, and the body reads that field. The same return-type shorthands the getter macros support apply here: the `&str` return makes the field `Value` a `String` and appends `.as_str()`, just as `#[cgp_auto_getter]` does. When a getter trait has several methods, the `UseFields` impl carries one `HasField` bound and one method body per getter, each keyed by its own method name. The impl is paired with a matching `IsProviderFor` impl carrying the same bounds, so the [check traits](../../concepts/check-traits.md) can report a missing field precisely. - -This is one of three provider impls `#[cgp_getter]` generates for a getter component, the other two being [`UseField`](use_field.md) for a wiring-chosen field name and [`WithProvider`](with_provider.md) for adapting a foundational field getter. A context picks among them at wiring time: `UseFields` when method and field names coincide, `UseField` when they differ. - -## Examples - -A context whose field name matches the getter method can be wired to `UseFields` to get the auto-getter convention inside a `#[cgp_getter]` component. The method `foo` and the field `foo` share a name: - -```rust -use cgp::prelude::*; - -#[cgp_getter] -pub trait HasFoo { - fn foo(&self) -> &str; -} - -#[derive(HasField)] -pub struct App { - pub foo: String, -} - -delegate_components! { - App { - FooGetterComponent: UseFields, - } -} - -fn describe(app: &App) -> &str { - app.foo() // reads the `foo` field -} -``` - -Because `App` wires `FooGetterComponent` to `UseFields`, the generated impl reads `App`'s `foo` field — the field whose name equals the method `foo` — to implement the getter. If the value were instead stored under a differently named field, this wiring would not apply, and the context would wire `UseField` with the actual field name instead. - -## Related constructs - -`UseFields` is generated by [`#[cgp_getter]`](../macros/cgp_getter.md) and is the provider analogue of the blanket impl emitted by [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md). It sits beside [`UseField`](use_field.md), which keys on a wiring-chosen tag instead of the method name, and [`WithProvider`](with_provider.md), the third getter provider `#[cgp_getter]` emits. The field reads it performs go through [`HasField`](../traits/has_field.md), keyed by [`Symbol!`](../macros/symbol.md) on the method name and produced by [`#[derive(HasField)]`](../derives/derive_has_field.md). A context selects `UseFields` through [`delegate_components!`](../macros/delegate_components.md) and verifies the wiring with [`check_components!`](../macros/check_components.md). - -## Source - -- The struct is defined in [crates/core/cgp-component/src/providers/use_fields.rs](../../../crates/core/cgp-component/src/providers/use_fields.rs). -- The `UseFields` impl is built by `to_use_fields_impl` in [crates/macros/cgp-macro-core/src/types/cgp_getter/to_use_fields_impl.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_getter/to_use_fields_impl.rs), which keys each method on its name via `Symbol::from_ident` and emits a matching `HasField` bound. -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_getter](../../implementation/entrypoints/cgp_getter.md). diff --git a/docs/reference/providers/use_type.md b/docs/reference/providers/use_type.md deleted file mode 100644 index 6c3570cf..00000000 --- a/docs/reference/providers/use_type.md +++ /dev/null @@ -1,102 +0,0 @@ -# `UseType` (provider) - -`UseType` is a zero-sized provider that supplies a concrete `Type` as the value of an abstract CGP type, letting a context bind an abstract associated type to a real type purely through wiring. - -This document describes the `UseType` **provider** — the struct `UseType(PhantomData)`. It is a different construct from the [`#[use_type]` attribute](../attributes/use_type.md), which is a modifier that rewrites bare type names inside `#[cgp_fn]`, `#[cgp_impl]`, and `#[cgp_component]` definitions and adds the owning trait as a bound. The attribute is about *referring to* an abstract type ergonomically; the provider here is about *choosing the concrete type* an abstract type resolves to. They share a name because both center on abstract types, but they live in different places and do different jobs — link between them, do not conflate them. - -## Purpose - -`UseType` removes the need to hand-write a provider impl every time a context wants to fix an abstract type to a concrete one. An abstract type in CGP is a trait with a single associated type, defined with [`#[cgp_type]`](../macros/cgp_type.md) — for example `trait HasScalarType { type Scalar; }`. Generic code refers to `Self::Scalar` without committing to any particular type, and a concrete context decides what `Scalar` actually is. Without `UseType`, making that decision would mean writing a bespoke provider whose only content is `type Scalar = f64;`, repeated for every abstract type and every concrete choice. - -`UseType` is the one provider that captures this trivial shape once and for all: it is a [`TypeProvider`](../components/has_type.md) that reports its type parameter `T` as the abstract type. Wiring a context's type component to `UseType` therefore sets that context's abstract type to `f64` with no custom impl. This is the type-level counterpart of how [`UseField`](use_field.md) lets a getter read an arbitrary field by name — a general-purpose provider parameterized by exactly the thing the context wants to supply. - -Because the provider carries no runtime data — it is a type-level marker, `PhantomData` and nothing more — `UseType` exists only to be named in a delegation table. It is never constructed as a value during normal use. - -## Definition - -`UseType` is a phantom-typed struct parameterized by the concrete type it supplies, defined in `cgp-type`: - -```rust -pub struct UseType(pub PhantomData); - -pub type WithType = WithProvider>; -``` - -The `WithType` alias wraps `UseType` in [`WithProvider`](with_provider.md), the adapter that lets a generic `TypeProvider` stand in as the provider for a specific `#[cgp_type]` component. A `#[cgp_type]` macro generates a `WithProvider` impl for its component, so wiring with either `UseType` (via the component's generated `UseType` impl) or `WithType` (via `WithProvider`) sets the abstract type to `T`. - -## Behavior - -`UseType` implements the built-in provider trait [`TypeProvider`](../components/has_type.md) for every context and tag, setting its associated `Type` to the struct's own type parameter: - -```rust -#[cgp_provider(TypeProviderComponent)] -impl TypeProvider for UseType { - type Type = Type; -} -``` - -The impl is unconditional in `Context` and `Tag` — `UseType` is a `TypeProvider` whose `Type` is `f64` regardless of which context or which type tag asks. `HasType` is the consumer trait that reads this; `TypeProvider` is its provider trait. So once a context's `TypeProviderComponent` is wired to `UseType`, the context implements `HasType` with `Type = f64`, and `TypeOf` resolves to `f64`. - -This same provider is what [`#[cgp_type]`](../macros/cgp_type.md) targets. When you write `#[cgp_type] trait HasScalarType { type Scalar; }`, the macro generates a `UseType` impl for the component's provider trait: - -```rust -impl ScalarTypeProvider<__Context__> for UseType { - type Scalar = Scalar; -} -``` - -so that wiring `ScalarTypeProviderComponent` to `UseType` sets `Scalar = f64` on the context. The built-in `TypeProvider` impl shown above and the per-component impl generated by `#[cgp_type]` are the two faces of the same `UseType` struct: the first makes it a provider for the built-in `HasType` component, the second makes it a provider for a user-defined abstract-type component. A bound on the associated type (such as `type Scalar: Copy`) is copied into the generated impl's `where` clause, so the concrete type must satisfy it at the wiring site. - -## Examples - -A complete use defines an abstract type, wires a concrete type with `UseType`, and reads it back through `HasScalarType`: - -```rust -use cgp::prelude::*; - -#[cgp_type] -pub trait HasScalarType { - type Scalar: Copy; -} - -pub struct App; - -delegate_components! { - App { - ScalarTypeProviderComponent: UseType, - } -} - -fn zero() -> Context::Scalar -where - Context: HasScalarType, - Context::Scalar: Default, -{ - Default::default() -} -``` - -`App` wires `ScalarTypeProviderComponent` to `UseType`, so the generated `UseType` impl makes `App` implement `HasScalarType` with `Scalar = f64`. The `Copy` bound on the associated type is enforced against `f64` where the wiring is written. - -The same binding can be expressed with the `WithType` alias, which routes through `WithProvider` instead of the component's own `UseType` impl: - -```rust -delegate_components! { - App { - ScalarTypeProviderComponent: WithType, - } -} -``` - -Both forms produce the same result — `App::Scalar` is `f64` — which is why `UseType` is the idiomatic way to bind an abstract type without writing a provider by hand. - -## Related constructs - -`UseType` is the provider that [`#[cgp_type]`](../macros/cgp_type.md) generates an impl for, so the two are almost always seen together: `#[cgp_type]` defines the abstract type and `UseType` supplies the concrete one at wiring time. It implements the built-in [`HasType` / `TypeProvider`](../components/has_type.md) component, the foundation on which all abstract types rest. Its `WithType` alias is one of the named wrappers around [`WithProvider`](with_provider.md). It is the type-level analogue of the [`UseField`](use_field.md) provider that [`#[cgp_getter]`](../macros/cgp_getter.md) generates, and of [`UseDelegate`](use_delegate.md) for behavioral components. For resolving an abstract type through a lookup table rather than a fixed type, see [`UseDelegatedType`](use_delegated_type.md). Do not confuse this provider with the similarly named [`#[use_type]` attribute](../attributes/use_type.md), which imports and rewrites abstract type names in definitions. - -## Source - -- The `UseType` struct, its `WithType` alias, and the built-in `TypeProvider` impl are in [crates/core/cgp-type/src/impls/use_type.rs](../../../crates/core/cgp-type/src/impls/use_type.rs). -- The `HasType` consumer trait, the `TypeProvider` provider trait, and the `TypeOf` alias are in [crates/core/cgp-type/src/traits/has_type.rs](../../../crates/core/cgp-type/src/traits/has_type.rs). -- The `#[cgp_type]`-generated `UseType` impl is built in [crates/macros/cgp-macro-core/src/types/cgp_type/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_type/item.rs). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_type](../../implementation/entrypoints/cgp_type.md). diff --git a/docs/reference/providers/with_provider.md b/docs/reference/providers/with_provider.md deleted file mode 100644 index 89b3e7a6..00000000 --- a/docs/reference/providers/with_provider.md +++ /dev/null @@ -1,99 +0,0 @@ -# `WithProvider` - -`WithProvider` is a zero-sized adapter provider that turns a foundational provider — one implementing `TypeProvider` or `FieldGetter` — into a provider of a specific CGP component. - -## Purpose - -`WithProvider` bridges the gap between CGP's two layers of provider trait. Foundational traits like [`TypeProvider`](../components/has_type.md) and [`FieldGetter`](../traits/has_field.md) are generic, component-agnostic mechanisms: a `TypeProvider` supplies *some* abstract type for *some* tag, and a `FieldGetter` reads *some* field for *some* output tag, without either knowing which named component it serves. A CGP component, by contrast, has a specific provider trait — `NameTypeProvider`, `NameGetter` — that a context wires to. `WithProvider` is the adapter that lets a foundational provider stand in as the provider for one of these named components: it implements the component's provider trait by forwarding to the foundational provider's method. - -This adapter is what lets the foundational layer be wired without each foundational provider having to implement every component trait by hand. A field getter implemented once as a `FieldGetter` can serve any number of getter components through `WithProvider`; an abstract-type implementation written once as a `TypeProvider` can serve any type component the same way. The component-specific glue is generated, and `WithProvider` is the type that carries it. - -`WithProvider` is rarely written in full by users, because its common uses are packaged as aliases. The family `WithContext`, `WithType`, `WithField`, `WithFieldRef`, and `WithDelegatedType` are all `WithProvider<...>` specialized to a particular inner provider, and these aliases are what appears in everyday wiring. Understanding `WithProvider` is what explains why those aliases work. - -Like every CGP provider, `WithProvider` carries no runtime value. The `Provider` type parameter is held in `PhantomData`, and the struct exists only as a type-level marker naming the foundational provider to adapt. - -## Definition - -`WithProvider` is a struct parameterized by the inner provider, defined in `cgp-component`: - -```rust -pub struct WithProvider(pub PhantomData); -``` - -The single type parameter `Provider` is the foundational provider being adapted — typically a [`TypeProvider`](../components/has_type.md) or [`FieldGetter`](../traits/has_field.md). The `PhantomData` makes `Provider` a parameter of a valueless struct; nothing of `Provider` is ever constructed. - -## Behavior - -`#[cgp_type]` and `#[cgp_getter]` generate a `WithProvider` impl that forwards a component's provider-trait method to the inner provider's foundational method. For a type component such as - -```rust -#[cgp_type] -pub trait HasNameType { - type Name; -} -``` - -`#[cgp_type]` emits a `WithProvider` impl that defines the component's associated type from the inner `TypeProvider` (shown with the macro's real placeholder identifiers): - -```rust -impl<__Provider__, Name, __Context__> NameTypeProvider<__Context__> for WithProvider<__Provider__> -where - __Provider__: TypeProvider<__Context__, NameTypeProviderComponent, Type = Name>, -{ - type Name = Name; -} -``` - -For a single-method getter, `#[cgp_getter]` emits an analogous `WithProvider` impl that reads the value through the inner `FieldGetter`: - -```rust -impl<__Context__, __Provider__> NameGetter<__Context__> for WithProvider<__Provider__> -where - __Provider__: FieldGetter<__Context__, NameGetterComponent, Value = String>, -{ - fn name(__context__: &__Context__) -> &str { - __Provider__::get_field(__context__, PhantomData::).as_str() - } -} -``` - -In both cases the bound names the foundational trait — `TypeProvider` or `FieldGetter` — keyed by the component-name struct, and the method or associated type forwards to it. `#[cgp_getter]` generates the `WithProvider` impl only when the getter trait has exactly one method, since a single foundational getter cannot serve several methods at once. Each impl is paired with a matching `IsProviderFor` impl so dependencies reach the [check traits](../../concepts/check-traits.md). - -The aliases specialize `WithProvider` to a fixed inner provider so the common cases need no `WithProvider<...>` spelled out. `WithContext = WithProvider` adapts the context's own consumer-trait implementation; `WithType = WithProvider>` and `WithField = WithProvider>` adapt the foundational type and field providers; `WithFieldRef = WithProvider>` adapts a getter that returns a reference borrowed through `AsRef`; and `WithDelegatedType = WithProvider>` adapts a type provider that looks its type up in a delegation table. `WithContext` lives in `cgp-component` beside `WithProvider`, while the `WithType`/`WithDelegatedType` pair is defined in `cgp-type` and the `WithField`/`WithFieldRef` pair in `cgp-field`, each next to the inner provider it wraps. - -## Examples - -The everyday way to use `WithProvider` is through one of its aliases, which read as a single wiring choice. Adapting the context's own field getter into a getter component uses `WithField`: - -```rust -use cgp::prelude::*; - -#[cgp_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[derive(HasField)] -pub struct Person { - pub first_name: String, -} - -delegate_components! { - Person { - NameGetterComponent: WithField, - } -} -``` - -`WithField` expands to `WithProvider>`, so `Person`'s `NameGetter` provider is the adapter wrapping the foundational `UseField` getter for the `first_name` field. The generated `WithProvider` impl forwards `name()` to `UseField`'s `FieldGetter::get_field`, which reads `first_name`. The same shape recurs for abstract types: wiring a type component to `WithType` adapts `UseType`, and to `WithDelegatedType` adapts a `UseDelegatedType` that resolves the type through a table. - -## Related constructs - -`WithProvider`'s impls are generated by [`#[cgp_type]`](../macros/cgp_type.md) and [`#[cgp_getter]`](../macros/cgp_getter.md), adapting the foundational [`TypeProvider`](../components/has_type.md) and [`FieldGetter`](../traits/has_field.md) traits into component providers. Its alias family wraps the foundational providers documented separately: [`UseContext`](use_context.md) via `WithContext`, [`UseType`](use_type.md) via `WithType`, [`UseField`](use_field.md) via `WithField`, [`UseFieldRef`](use_field_ref.md) via `WithFieldRef`, and [`UseDelegatedType`](use_delegated_type.md) via `WithDelegatedType`. Aliases are wired with [`delegate_components!`](../macros/delegate_components.md), and the dependency propagation that makes them checkable flows through [`IsProviderFor`](../traits/is_provider_for.md). - -## Source - -- The struct is defined in [crates/core/cgp-component/src/providers/with_provider.rs](../../../crates/core/cgp-component/src/providers/with_provider.rs), and the `WithContext` alias in [crates/core/cgp-component/src/providers/use_context.rs](../../../crates/core/cgp-component/src/providers/use_context.rs). -- The remaining aliases are defined beside their inner providers: `WithType` and `WithDelegatedType` in [crates/core/cgp-type/src/impls/use_type.rs](../../../crates/core/cgp-type/src/impls/use_type.rs) and [use_delegated_type.rs](../../../crates/core/cgp-type/src/impls/use_delegated_type.rs), and `WithField` and `WithFieldRef` in [crates/core/cgp-field/src/impls/use_field.rs](../../../crates/core/cgp-field/src/impls/use_field.rs) and [use_ref.rs](../../../crates/core/cgp-field/src/impls/use_ref.rs). -- The component `WithProvider` impls are generated by `#[cgp_type]` in [crates/macros/cgp-macro-core/src/types/cgp_type/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_type/item.rs) and by `#[cgp_getter]` in [crates/macros/cgp-macro-core/src/types/cgp_getter/with_provider.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_getter/with_provider.rs). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_getter](../../implementation/entrypoints/cgp_getter.md). diff --git a/docs/reference/traits/can_use_component.md b/docs/reference/traits/can_use_component.md deleted file mode 100644 index 06384733..00000000 --- a/docs/reference/traits/can_use_component.md +++ /dev/null @@ -1,100 +0,0 @@ -# `CanUseComponent` - -`CanUseComponent` is the context-side check trait that holds whenever a context both delegates a component and its chosen provider is a valid provider for it, giving `check_components!` a single bound to assert that forces readable wiring errors. - -## Purpose - -`CanUseComponent` exists to answer one question about a context: "can this context actually use this component, with every dependency satisfied?" That question is deliberately not the same as "does the context implement the consumer trait?". Asking the consumer-trait question directly makes the compiler report the outermost unmet bound — usually a bare "the provider does not implement the provider trait" — and hide the indirect reasoning behind it, because the provider blanket impl is a competing candidate that suppresses detailed diagnostics. The root cause, often a single missing getter or abstract type, never appears. - -`CanUseComponent` reframes the question along a path Rust will explain. It is satisfied only when two things hold together: the context delegates the component (it has a [`DelegateComponent`](delegate_component.md) entry for it), and the delegated provider satisfies [`IsProviderFor`](is_provider_for.md) for that exact context and parameter set. Because `IsProviderFor` carries the provider's real `where` bounds, requiring it forces the compiler to evaluate and report those bounds. The detailed error that was hidden behind the consumer-trait question becomes visible once the same wiring is probed through `CanUseComponent` instead. - -The trait is mechanism, not API. Application code never names it; it is the bound that [`check_components!`](../macros/check_components.md) emits so that a wiring mistake is reported at the wiring site, pinpointing the absent dependency, rather than erupting at some distant call to the consumer trait. - -## Definition - -`CanUseComponent` is an empty marker trait parameterized by a component and a parameter tuple, with a single blanket impl supplying all of its instances: - -```rust -pub trait CanUseComponent {} - -impl CanUseComponent for Context -where - Context: DelegateComponent, - Context::Delegate: IsProviderFor, -{ -} -``` - -`Self` is the context being checked. `Component` is the component-name type, the same key used in the delegation table, and `Params` collects the component's extra generic parameters — a single parameter directly, several as a tuple, and the unit tuple `()` (the default) when there are none. The trait has no methods and no associated items; its meaning lives entirely in the blanket impl's `where` clause. - -## Behavior - -The blanket impl is the whole behavior, and its two bounds are the two halves of "can use". The first, `Context: DelegateComponent`, requires the context to have a table entry for the component — without a delegation there is nothing to use, and the lookup fails with the `DelegateComponent` diagnostic about a missing entry. The second, `Context::Delegate: IsProviderFor`, requires the delegated provider to be a genuine provider for this component, for this context, at these parameters. This is where dependency checking happens: `IsProviderFor` carries the provider's `where` bounds, so the compiler must satisfy them to grant `CanUseComponent`, and an unmet bound is reported in full. - -`CanUseComponent` is mirror to [`IsProviderFor`](is_provider_for.md): it asks the same readability-restoring question, but indexed on the context (`Self = Context`) rather than the provider (`Self = Provider`). One starts from "the context delegates and the delegate is valid"; the other from "this specific provider is valid". `check_components!` uses `CanUseComponent` for its default, context-oriented checks and switches to `IsProviderFor` for its `#[check_providers(...)]` form, when a particular provider must be verified independently. - -Routing a check through `CanUseComponent` rather than the consumer trait is what makes errors legible. Because the blanket impl threads the dependency bounds through `IsProviderFor`, an unsatisfied transitive requirement is surfaced and attributed to the actual missing piece. The trait is empty and the impl is unconditional in form, so satisfying it costs nothing at runtime; the only effect is the compile-time obligation its `where` clause imposes. - -The two bounds also distinguish the two ways wiring can be wrong, and the diagnostics differ accordingly. A context that never delegated the component fails the first bound, and the `DelegateComponent` `on_unimplemented` note reports a missing table entry — the fix is to add the wiring. A context that delegated the component to a provider whose dependencies are unmet passes the first bound but fails the second, and the error is the provider's own unsatisfied `where` clause, carried up through `IsProviderFor` — the fix is to supply the missing dependency. Reading which bound failed tells a reader whether the wiring is absent or merely incomplete. - -`check_components!` consumes the trait by emitting a private check trait whose supertrait is `CanUseComponent` and then one empty impl of that trait per checked entry; each impl compiles only if its `CanUseComponent` supertrait holds, so the entire table is an assertion that every listed component is usable. Because the assertion is a compile-time construct that produces no values, a successful build is the passing test and the macro adds nothing to the binary. - -## Examples - -`CanUseComponent` is asserted, never called. A `check_components!` table reduces to one impl of an internal check trait whose supertrait is `CanUseComponent`, so the impl compiles only if the bound holds: - -```rust -use cgp::prelude::*; - -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasName, -{ - fn greet(&self) { - println!("Hello, {}!", self.name()); - } -} - -#[derive(HasField)] -pub struct Person { - pub first_name: String, // not `name` -} - -delegate_components! { - Person { GreeterComponent: GreetHello } -} - -check_components! { - Person { GreeterComponent } -} -``` - -The `check_components!` table forces the assertion `Person: CanUseComponent`. Resolving it requires `Person: DelegateComponent` (satisfied by the wiring) and `GreetHello: IsProviderFor` (not satisfied, because `GreetHello` needs `HasName` and `Person` has no `name` field). The compiler reports the absent `name` field at the check, rather than at a future `person.greet()` call. Writing the same bound directly probes the wiring without the macro: - -```rust -fn assert_wiring() -where - Person: CanUseComponent, -{} -``` - -## Related constructs - -`CanUseComponent` is the bound that [`check_components!`](../macros/check_components.md) asserts to verify a context's wiring, and the macro's whole purpose is to route checking through it for readable errors. Its blanket impl is built directly on [`DelegateComponent`](delegate_component.md) — the context must delegate the component — and [`IsProviderFor`](is_provider_for.md) — the delegate must be a valid provider — so it ties the two wiring traits together into a single context-side question. It is the context-indexed counterpart of `IsProviderFor`, which `check_components!`'s `#[check_providers(...)]` form asserts on providers directly when a higher-order provider stack needs each layer checked. The components it checks are defined by [`#[cgp_component]`](../macros/cgp_component.md) and wired by [`delegate_components!`](../macros/delegate_components.md). - -## Source - -- The trait and its sole blanket impl are defined in [crates/core/cgp-component/src/traits/can_use_component.rs](../../../crates/core/cgp-component/src/traits/can_use_component.rs) and re-exported through [crates/core/cgp-component/src/macro_prelude.rs](../../../crates/core/cgp-component/src/macro_prelude.rs). -- The checks that assert it are generated by `check_components!`, whose codegen lives in [crates/macros/cgp-macro-core/src/types/check_components/](../../../crates/macros/cgp-macro-core/src/types/check_components/) — `table.rs` chooses `CanUseComponent` versus `IsProviderFor` as the check trait's supertrait. -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/check_components.md](../../implementation/entrypoints/check_components.md). diff --git a/docs/reference/traits/cast.md b/docs/reference/traits/cast.md deleted file mode 100644 index 63fe3c18..00000000 --- a/docs/reference/traits/cast.md +++ /dev/null @@ -1,110 +0,0 @@ -# Casting: `CanUpcast`, `CanDowncast`, `CanDowncastFields`, `CanBuildFrom` - -`CanUpcast`, `CanDowncast`, `CanDowncastFields`, and `CanBuildFrom` are the structural conversion traits that move a value between two CGP data types by their shared fields or variants — widening an enum, narrowing it, or assembling a struct from the fields of others — entirely by matching type-level names. - -## Purpose - -These traits exist so that two types sharing a subset of named fields or variants can be converted into one another generically, without any hand-written `From`/`TryFrom` impl. CGP represents every record as a product of named fields and every enum as a sum of named variants (via [`#[derive(HasFields)]`](../derives/derive_has_fields.md)), and once a type's shape is a type-level list, conversion becomes a matter of routing each named entry to the matching slot in the target. The four traits cover the directions that routing can take: enlarging a sum, shrinking a sum, and growing a record out of smaller records. - -Upcast and downcast are the two directions of variant conversion. An *upcast* takes a value of a "narrow" enum whose variants are a subset of a "wide" enum's, and lifts it into the wide enum — always succeeds, because every variant of the source has a home in the target. A *downcast* goes the other way: it tries to narrow a wide enum into a smaller one, succeeding only if the value's current variant exists in the target, and otherwise handing back a remainder so the caller can keep trying other targets. `CanBuildFrom` is the record counterpart: it assembles a target by copying the fields it shares with a source, leaving the rest of the target to be filled in separately. - -## Definition - -`CanUpcast` consumes a value and produces the wider `Target` directly, since an upcast cannot fail. The `PhantomData` argument only names the target type for inference: - -```rust -pub trait CanUpcast { - fn upcast(self, _tag: PhantomData) -> Target; -} -``` - -`CanDowncast` is fallible. It returns `Result`, where the `Remainder` is the source's extractor with the attempted variants removed — what is left to try if this narrowing did not match: - -```rust -pub trait CanDowncast { - type Remainder; - - fn downcast(self, _tag: PhantomData) -> Result; -} -``` - -`CanDowncastFields` is the same operation one level down, implemented directly on an extractor (a partial-sum type) rather than on the original enum. It is what you call on a `Remainder` returned by a previous `downcast` to attempt the next target without re-wrapping: - -```rust -pub trait CanDowncastFields { - type Remainder; - - fn downcast_fields(self, _tag: PhantomData) -> Result; -} -``` - -`CanBuildFrom` assembles a target builder by drawing fields from a source. It is implemented for a builder and consumes a `Source`, moving every field the two share out of the source and into the builder, returning the updated builder as `Output`: - -```rust -pub trait CanBuildFrom { - type Output; - - fn build_from(self, source: Source) -> Self::Output; -} -``` - -## Behavior - -The variant conversions are driven by a shared recursion over the target's sum of variants. `CanUpcast` is implemented for any context that has both a [`HasFields`](has_fields.md) shape and a [`HasExtractor`](extract_field.md): it turns the source into its extractor, then walks the source's own field list, extracting each variant from the extractor and reconstructing it into the target via [`FromVariant`](from_variant.md). Because every source variant is guaranteed to exist in a wider target, the walk is total and the remaining extractor is uninhabited, finalized away with [`FinalizeExtract`](extract_field.md). The result is the source value re-tagged as the target enum. - -`CanDowncast` and `CanDowncastFields` recurse over the *target's* variants instead. For each `Field` in `Target::Fields`, the implementation uses [`ExtractField`](extract_field.md) to try pulling that variant out of the source extractor: on success it rebuilds the target with `FromVariant` and returns `Ok`; on failure it threads the shrunken remainder into the next variant's attempt. If no target variant matches, the terminal `Void` impl returns the whole remainder as `Err`. The difference between the two traits is only the starting point — `CanDowncast` first calls `to_extractor` on the original enum, while `CanDowncastFields` operates on an extractor it is already given, which is exactly the `Remainder` from a prior `downcast`. This is why downcasting against several candidate targets in turn chains a `downcast` followed by `downcast_fields` calls on each remainder. - -`CanBuildFrom` recurses over the source's field product. For each `Field` the source exposes, it uses [`TakeField`](has_builder.md) to remove that field's value from the source and [`BuildField`](has_builder.md) to write it into the target builder, threading both the shrinking source and the growing builder through the recursion. When the source's fields are exhausted, the target builder is returned. The target need not be complete after one `build_from`: a builder can absorb fields from several sources in sequence, and only then be finalized. - -## Examples - -Upcasting and downcasting let independently-defined enums interconvert by their common variants. Given a wide enum and a narrow one that share variant names, conversion is name-driven and needs no manual impl: - -```rust -use cgp::prelude::*; -use core::marker::PhantomData; - -#[derive(Debug, Eq, PartialEq, CgpData)] -pub enum FooBar { - Foo(u64), - Bar(String), -} - -#[derive(Debug, Eq, PartialEq, CgpData)] -pub enum FooBarBaz { - Foo(u64), - Bar(String), - Baz(bool), -} - -// upcast: always succeeds, every FooBar variant exists in FooBarBaz -let wide = FooBar::Foo(1).upcast(PhantomData::); -assert_eq!(wide, FooBarBaz::Foo(1)); - -// downcast: succeeds for shared variants, fails for Baz -assert_eq!(FooBarBaz::Bar("hi".into()).downcast(PhantomData::).ok(), Some(FooBar::Bar("hi".into()))); -assert_eq!(FooBarBaz::Baz(true).downcast(PhantomData::).ok(), None); -``` - -`CanBuildFrom` assembles one struct from several smaller ones by copying their fields into a builder before finalizing: - -```rust -#[derive(CgpData)] pub struct FooBar { pub foo: u64, pub bar: String } -#[derive(CgpData)] pub struct Baz { pub baz: bool } -#[derive(CgpData)] pub struct FooBarBaz { pub foo: u64, pub bar: String, pub baz: bool } - -let combined: FooBarBaz = FooBarBaz::builder() - .build_from(FooBar { foo: 1, bar: "bar".into() }) - .build_from(Baz { baz: true }) - .finalize_build(); -``` - -## Related constructs - -The casting traits sit on top of the extensible-data primitives: variant casts route through [`ExtractField`](extract_field.md), [`FromVariant`](from_variant.md), and [`FinalizeExtract`](extract_field.md) over the [`Either`](../types/either.md)/`Void` sum spine, while `CanBuildFrom` routes through [`HasBuilder`](has_builder.md)'s `TakeField`/`BuildField` over the [`Cons`](../types/cons.md)/`Nil` product spine. All four depend on the type's [`HasFields`](has_fields.md) shape, which is what [`#[derive(CgpData)]`](../derives/derive_cgp_data.md) generates along with the builders and extractors these conversions consume. The names matched during casting are the [`Symbol!`](../macros/symbol.md) tags on each field or variant. The conceptual overviews that frame these conversions are [extensible records](../../concepts/extensible-records.md) (for `CanBuildFrom`) and [extensible variants](../../concepts/extensible-variants.md) (for upcasting and downcasting); `build_from` is used to assemble a context in the [application builder](../../examples/application-builder.md) example, `CanUpcast` to construct partial enums in the [expression interpreter](../../examples/expression-interpreter.md) example, and both `upcast` and `downcast` to convert between sibling shape enums in the [extensible shapes](../../examples/extensible-shapes.md) example. - -## Source - -- `CanUpcast`, `CanDowncast`, and `CanDowncastFields`, together with the internal `FieldsExtractor` recursion that drives them, are defined in [crates/core/cgp-field/src/impls/cast.rs](../../../crates/core/cgp-field/src/impls/cast.rs). -- `CanBuildFrom` and its internal `FieldsBuilder` recursion are in [crates/core/cgp-field/src/impls/build_from.rs](../../../crates/core/cgp-field/src/impls/build_from.rs). -- The underlying extractor and builder traits are under [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/) (`extract_field.rs`, `from_variant.rs`, `has_builder.rs`). diff --git a/docs/reference/traits/default_namespace.md b/docs/reference/traits/default_namespace.md deleted file mode 100644 index 6bf9442f..00000000 --- a/docs/reference/traits/default_namespace.md +++ /dev/null @@ -1,108 +0,0 @@ -# `DefaultNamespace`, `DefaultImpls1`, `DefaultImpls2` - -`DefaultNamespace`, `DefaultImpls1`, and `DefaultImpls2` are the hierarchical lookup traits that back namespaces and presets, each mapping a key to a `Delegate` type so that a context can inherit a whole group of default wirings and resolve them by component name, by one type parameter, or by two. - -## Purpose - -This family exists to give namespaces and presets a uniform, per-key lookup surface. A namespace is a reusable table of default wirings that a context can opt into and then selectively override; resolving such a default means asking, "for this key, what does the namespace delegate to?" The three traits are the answer-bearers, differing only in how many type parameters take part in the key. `DefaultNamespace` keys a default purely on the component name. `DefaultImpls1` keys it on the component name *and* one further type — the typical shape for a per-type default, where the same component resolves differently for `String` than for `u64`. `DefaultImpls2` does the same for two further types, for components parameterized by a pair. - -The reason to have all three rather than one variadic trait is that each fixes the arity of the key at the type level, which lets the projection `>` resolve cleanly. A context that joins a namespace forwards its lookups into one of these traits, and a `for … in` loop that pulls per-type defaults reads them by projecting the `Delegate`. The whole mechanism is type-level and inheritance-with-override in spirit: a directly-wired entry on a context wins over a namespace fallback, exactly as a preset is meant to be customizable. - -These traits are the plumbing beneath the [`#[cgp_namespace]`](../macros/cgp_namespace.md) macro and the `namespace` / `for … in` syntax of [`delegate_components!`](../macros/delegate_components.md). A user writing namespaces names them only in the namespace header and in the `for … in` loop target; the macros generate the impls and the forwarding. - -## Definition - -All three are key-value lookup traits carrying a single `Delegate` associated type, differing only in how many lookup-type parameters precede the `Components` table parameter: - -```rust -pub trait DefaultNamespace { - type Delegate; -} - -pub trait DefaultImpls1 { - type Delegate; -} - -pub trait DefaultImpls2 { - type Delegate; -} -``` - -`Self` is the key being looked up — a component-name type for `DefaultNamespace`, and the component-name type for the `DefaultImpls` variants too, with the per-instance types carried as the leading parameters. `Components` is the table the lookup is performed against, threaded through so that the same key can resolve differently depending on which context's table is consulted. The leading `T`, or `T1`/`T2`, are the instance types a per-type or per-pair default is keyed on. `Delegate` is the resolved value: the provider (or further redirect) the key maps to. As with [`DelegateComponent`](delegate_component.md), there is no method and no data; resolution is the projection of `Delegate` from the matching impl. - -## Behavior - -Each trait is implemented once per default entry, and resolving a default is reading `Delegate` from the matching impl. `DefaultNamespace` is implemented for a component-name key when a namespace supplies a default for that component regardless of any type parameter; the [`#[prefix(...)]`](../macros/cgp_namespace.md) attribute that attaches a component to a namespace emits exactly such an impl, with `Delegate` a [`RedirectLookup`](../providers/redirect_lookup.md) that re-routes the lookup along a path. `DefaultImpls1` is implemented for a component-name key carrying one instance type `T`, so the same component resolves per type; the `#[default_impl(T in DefaultImpls1)]` attribute on a provider impl (shown under Examples below) registers the provider as the default for that `T`, emitting `impl DefaultImpls1 for T { type Delegate = Provider; }`. `DefaultImpls2` does the same with two instance types for a pair-parameterized component. - -The registration impl carries only the parameters that name the key and provider plus the `Components` table — never the provider's impl-side `where` clause. A provider whose bounds come from `#[use_type]`, `#[uses]`, `#[implicit]`, or `#[use_provider]` (for example `where Self: HasErrorType`) registers cleanly: those bounds stay on the provider's own impl and its [`IsProviderFor`](is_provider_for.md), and are checked when a real context resolves the provider, so a per-type default works regardless of what abstract types the provider depends on. - -Where a `#[default_impl]` may be *written* is bounded by Rust's orphan rule, because the emitted impl is `impl Namespace<..> for Key`. A crate may register a default when it owns either the namespace trait or the key type. For an unprefixed component the key is the component's own marker, so a downstream crate that owns the component can register a default into a foreign namespace. For a [`#[prefix]`](../macros/cgp_namespace.md)-ed component the key is a `PathCons<..>` path built from `cgp`-owned path types and the component marker, so the impl is only orphan-legal in the crate that owns the namespace — a per-component default keyed on a prefix path is confined to the namespace's crate. This is why `#[default_impl]` couples a wiring to the namespace's crate; wiring that must live downstream of the namespace goes in the namespace body of whatever crate owns it instead. - -The hierarchical part is how a context consumes these defaults, which the [`delegate_components!`](../macros/delegate_components.md) `namespace` header and `for … in` syntax generate. A `namespace N;` header emits a blanket [`DelegateComponent`](delegate_component.md) impl on the context that forwards every key through `N`: `impl DelegateComponent for App where Key: N { type Delegate = Value; }`, paired with the matching [`IsProviderFor`](is_provider_for.md) forwarding so dependencies stay diagnosable. A `for in DefaultImpls1 { … }` loop emits a `DelegateComponent` impl keyed on a path whose `where` clause projects the default: `where T: DefaultImpls1`. Reading the loop: for each type `T` that has a `DefaultImpls1` default, wire that path to the projected `Provider`. The same loop works against a `DefaultNamespace`-style table or any namespace trait by changing the `in` target. - -Inheritance and override compose on top. A namespace that inherits from a parent (`new Child: DefaultNamespace { … }`) emits a blanket impl forwarding any key the parent resolves to the child, so the child resolves everything the parent does plus its own entries. A context's directly-wired entry resolves before the namespace fallback, so it shadows the inherited default for that key without disturbing the rest — the inheritance-with-override pattern presets rely on, expressed entirely through these projections with no runtime cost. - -## Examples - -A per-type default registered with `#[default_impl]` and then pulled into a context shows the chain. A provider declares itself the default for one type: - -```rust -use cgp::prelude::*; -use core::fmt::Display; - -#[cgp_component(ShowImpl)] -#[prefix(@test in DefaultNamespace)] -pub trait Show { - fn show(&self, value: &T) -> String; -} - -#[cgp_impl(new ShowString)] -#[default_impl(String in DefaultImpls1)] -impl ShowImpl { - fn show(&self, value: &String) -> String { - value.clone() - } -} -``` - -The `#[default_impl]` attribute emits `impl DefaultImpls1 for String { type Delegate = ShowString; }`, registering `ShowString` as the per-type default for `String`. A context then joins the namespace and pulls those defaults in with a `for … in` loop, optionally overriding one entry: - -```rust -pub struct App; - -delegate_components! { - App { - namespace DefaultNamespace; - - for in DefaultImpls1 { - @test.ShowImplComponent.T: Provider, - } - - @test.ShowImplComponent.u64: - ShowWithDisplay, // overrides the inherited default for u64 - } -} -``` - -The `namespace DefaultNamespace;` line forwards `App`'s lookups through `DefaultNamespace`, and the loop wires each `T` by projecting `T: DefaultImpls1`. The direct `u64` line shadows whatever the namespace would otherwise supply for that type. A namespace can also be defined wholesale and used as the loop target: - -```rust -cgp_namespace! { - new DefaultShowComponents { - [String, u64]: ShowWithDisplay, - } -} -``` - -Pointing a `for in DefaultShowComponents { … }` loop at this namespace wires the listed types to `ShowWithDisplay` through the same projection mechanism. - -## Related constructs - -`DefaultNamespace`, `DefaultImpls1`, and `DefaultImpls2` are the lookup traits the [`#[cgp_namespace]`](../macros/cgp_namespace.md) macro builds on, and they are consumed by the `namespace` header and `for … in` loop of [`delegate_components!`](../macros/delegate_components.md). Their `Delegate` entries are commonly a [`RedirectLookup`](../providers/redirect_lookup.md), which re-routes a lookup along a type-level path rather than naming a provider outright. A context's `namespace` header forwards through these traits into a blanket [`DelegateComponent`](delegate_component.md) impl, with the matching [`IsProviderFor`](is_provider_for.md) forwarding so dependency errors stay readable. For the broader picture of how namespaces and presets fit together, see [namespaces](../../concepts/namespaces.md). - -## Source - -- The three traits are defined in [crates/core/cgp-component/src/namespaces.rs](../../../crates/core/cgp-component/src/namespaces.rs), with `DefaultNamespace` re-exported through [crates/core/cgp-component/src/macro_prelude.rs](../../../crates/core/cgp-component/src/macro_prelude.rs). -- The namespace macro that builds the namespace trait and its inheritance impl lives in [crates/macros/cgp-macro-core/src/types/namespace/](../../../crates/macros/cgp-macro-core/src/types/namespace/); the `#[default_impl(... in DefaultImpls1<...>)]` attribute that registers a per-type default is parsed and lowered in [crates/macros/cgp-macro-core/src/types/attributes/default_impl/](../../../crates/macros/cgp-macro-core/src/types/attributes/default_impl/). -- The `namespace` header and `for … in` loop are handled by the `delegate_components!` codegen in [crates/macros/cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_namespace.md](../../implementation/entrypoints/cgp_namespace.md). diff --git a/docs/reference/traits/delegate_component.md b/docs/reference/traits/delegate_component.md deleted file mode 100644 index df134dba..00000000 --- a/docs/reference/traits/delegate_component.md +++ /dev/null @@ -1,114 +0,0 @@ -# `DelegateComponent` - -`DelegateComponent` is the core wiring trait that turns a type into a compile-time key→value table, mapping each `Key` to a single `Delegate` type so that component lookups can resolve to the provider that handles them. - -## Purpose - -`DelegateComponent` exists to record, at the type level, which provider a context has chosen for a given component. A CGP component splits the consumer trait callers use from the provider trait implementers write, but that split leaves one question open: for this context, *which* provider supplies the behavior? `DelegateComponent` answers it by being the single trait whose implementation stores that decision — one impl per entry, the component name as the key and the chosen provider as the value. - -The mental model is a type-level key-value map carried on the `Self` type. Implementing `DelegateComponent` for a type is "setting" the table's entry at `Key` to the associated `Delegate`; naming `Self: DelegateComponent` in a bound and reading `Self::Delegate` is "getting" the value back out. Because both the keys and the values are types, the entire lookup happens during type-checking and compiles down to nothing at runtime. This is the table that the provider blanket impl generated by [`#[cgp_component]`](../macros/cgp_component.md) consults to route a consumer call to a concrete provider. - -The reason this trait is the foundation rather than an internal detail is that the same table shape serves two distinct jobs. When the `Key` is a component-name type, a populated entry causes the context to inherit the corresponding provider trait through the blanket impl. When the `Key` is an arbitrary type — a shape, a tag, a path segment — the table is instead a plain dispatch table that a higher-order provider walks, with no provider trait involved. One trait covers both, which is why `DelegateComponent` underlies both ordinary wiring and the inner tables of providers like [`UseDelegate`](../providers/use_delegate.md). - -## Definition - -`DelegateComponent` is a single-method-free trait parameterized by a key, carrying one associated type: - -```rust -#[diagnostic::on_unimplemented( - message = "{Self} does not contain any DelegateComponent entry for {Key}", - note = "You might want to implement the provider trait for {Key} on {Self}" -)] -pub trait DelegateComponent { - type Delegate; -} -``` - -The `Key` parameter is the table key — the type being looked up. It is `?Sized` so that unsized marker types can serve as keys. The `Delegate` associated type is the value stored at that key: the provider (or further table) the entry resolves to. `Self` is the table itself, the type that owns the entry. There is no method and no data; the trait exists purely to associate a value type with a key type on a carrier type. The `#[diagnostic::on_unimplemented]` annotation tailors the compiler's error when a lookup misses, pointing the reader at the absent entry rather than at an opaque "trait not implemented". - -## Behavior - -A type can hold many `DelegateComponent` entries at once, one per distinct `Key`, and each is an independent impl. Reading the table is the act of writing `T: DelegateComponent` and projecting `>::Delegate`; the compiler resolves that projection to whichever value the matching impl declared. Because Rust forbids two impls of the same trait for the same `Self` and `Key`, each key maps to exactly one value, which is what makes the structure a genuine map rather than a relation. - -These impls are almost never written by hand. [`delegate_components!`](../macros/delegate_components.md) generates one `DelegateComponent` impl per table entry, taking the `Key: Value` line and emitting `impl DelegateComponent for Target { type Delegate = Value; }`. Alongside each one it also emits an [`IsProviderFor`](is_provider_for.md) impl that forwards the chosen provider's dependencies, so that a missing transitive requirement still surfaces as a readable error rather than a silent lookup miss; the two impls together are what makes a wired entry both resolvable and checkable. - -How the value is used depends entirely on what the key is. When the key is a component name such as `GreeterComponent`, the provider blanket impl from `#[cgp_component]` reads the entry and concludes that `Self`'s provider trait is implemented by following the delegate — so the context inherits the provider trait, and then the consumer trait, for that component. The blanket impl's body is itself a `DelegateComponent` lookup: it bounds the provider type by `DelegateComponent` and forwards each method call to `>::Delegate`, so the table read is literally how the call is routed. When the key is an arbitrary type, no provider trait attaches; the entry is just data in a lookup table. A `UseDelegate
` provider, for instance, dispatches on one of its generic parameters by looking that parameter up as a key in `Table`'s `DelegateComponent` entries, which is why the inner tables built by [`delegate_components!`](../macros/delegate_components.md)'s nested-table syntax key on shape or tag types instead of component names. - -Resolution is shallow by default: a single `DelegateComponent` read yields the immediate `Delegate`, and nothing forces that delegate to be a leaf provider rather than another table. Chaining happens when the delegate is itself a `DelegateComponent` carrier — an [aggregate provider](../../concepts/aggregate-providers.md) declared with `new` holds its own table, so a context can delegate a whole group of components to the bundle and let each component's blanket impl read through to the bundle's entry. The key types may also be type-level paths rather than plain names: [`RedirectLookup`](../providers/redirect_lookup.md) and the namespace machinery store entries keyed on `PathCons` lists, walking the table one segment at a time, which is the same `DelegateComponent` read applied to structured keys. - -## Examples - -A single wired component shows the table being set and then read. Given a component and a provider: - -```rust -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) { - println!("Hello!"); - } -} -``` - -a context wires it with `delegate_components!`, which emits the `DelegateComponent` entry: - -```rust -pub struct App; - -delegate_components! { - App { - GreeterComponent: GreetHello, - } -} -``` - -This expands to the hand-written equivalent of a single entry — the key is the component name, the value is the provider: - -```rust -impl DelegateComponent for App { - type Delegate = GreetHello; -} -``` - -Because `App` now delegates `GreeterComponent` to `GreetHello`, the provider blanket impl reads the entry and gives `App` the `Greeter` provider trait, and from there the consumer blanket impl gives `App` the `CanGreet` consumer trait, so `app.greet()` type-checks. - -An arbitrary-key table is the other use. The inner table built by the nested-table syntax keys on a shape type rather than a component name, so the same trait stores a dispatch map with no provider trait attached: - -```rust -delegate_components! { - new AreaComponents { - Rectangle: RectangleArea, - Circle: CircleArea, - } -} -``` - -Here `AreaComponents: DelegateComponent` and `AreaComponents: DelegateComponent` are plain lookup entries that a [`UseDelegate`](../providers/use_delegate.md) provider walks at dispatch time, choosing `RectangleArea` or `CircleArea` based on which shape it is asked about. Reading one entry back by hand is the same projection the macros generate internally: - -```rust -fn area_provider() -> PhantomData<>::Delegate> -where - AreaComponents: DelegateComponent, -{ - PhantomData -} -``` - -The bound `AreaComponents: DelegateComponent` is the "get", and `>::Delegate` is the value it returns — `RectangleArea` for `Shape = Rectangle`, `CircleArea` for `Shape = Circle`. - -## Related constructs - -`DelegateComponent` is the table that [`delegate_components!`](../macros/delegate_components.md) populates, one impl per entry. The provider blanket impl generated by [`#[cgp_component]`](../macros/cgp_component.md) is the reader: it looks a component name up in this table to find the provider whose trait the context inherits. The forwarding [`IsProviderFor`](is_provider_for.md) impl emitted next to each entry keeps missing dependencies diagnosable, and [`CanUseComponent`](can_use_component.md) combines the two — a context can use a component only if it both delegates the component here and the delegate is a valid provider for it. When the key is an arbitrary type rather than a component name, the table is the inner dispatch map consumed by [`UseDelegate`](../providers/use_delegate.md), and the path-keyed entries used by [`RedirectLookup`](../providers/redirect_lookup.md) and namespaces are `DelegateComponent` entries keyed on type-level paths. - -## Source - -- The trait is defined in [crates/core/cgp-component/src/traits/delegate_component.rs](../../../crates/core/cgp-component/src/traits/delegate_component.rs) and re-exported through the macro prelude in [crates/core/cgp-component/src/macro_prelude.rs](../../../crates/core/cgp-component/src/macro_prelude.rs). -- The impls are generated by `delegate_components!`, whose codegen lives in [crates/macros/cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/) — entry mapping and the `DelegateComponent`/`IsProviderFor` impl construction are in `mapping/eval.rs`. -- The provider blanket impl that reads the table is emitted by the `#[cgp_component]` codegen in [crates/macros/cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_component.md](../../implementation/entrypoints/cgp_component.md). diff --git a/docs/reference/traits/extract_field.md b/docs/reference/traits/extract_field.md deleted file mode 100644 index 04208d46..00000000 --- a/docs/reference/traits/extract_field.md +++ /dev/null @@ -1,125 +0,0 @@ -# `ExtractField` and the extractor trait family - -The extractor family is the set of traits that let an enum be matched one variant at a time, with the still-possible variants tracked in the type so that a chain of extractions becomes a provably exhaustive match without a wildcard arm. - -## Purpose - -The extractor family solves the problem of deconstructing a sum type generically and exhaustively. A `match` on a concrete enum names every variant in one place; the extractor instead pulls variants out one at a time, and each failed attempt narrows the set of remaining possibilities. The narrowing happens in the type: a failed extraction hands back a *remainder* whose type has one more variant marked impossible, and once every variant has been ruled out the remainder is an uninhabited type that can be discharged unconditionally. This is how generic code can match an arbitrary enum, variant by variant, and have the compiler confirm the match is complete. - -The family pairs an accessor that turns a value into an extractor with a per-variant extraction operation and a finalize step for the empty remainder. `HasExtractor` and its borrowed forms obtain the extractor; `ExtractField` tries one variant and returns either the payload or the shrunken remainder; `FinalizeExtract` discharges the remainder once it is uninhabited. The traits live in the field crate and are implemented for an enum by [`#[derive(ExtractField)]`](../derives/derive_extract_field.md), which generates the partial companion enums they operate on. - -## Definition - -The family consists of three accessor traits, the extraction operation, and two finalize traits. The accessors turn a value into an extractor in one of three ownership modes — owned, shared-reference, and mutable-reference: - -```rust -pub trait HasExtractor { - type Extractor; - fn to_extractor(self) -> Self::Extractor; - fn from_extractor(extractor: Self::Extractor) -> Self; -} - -pub trait HasExtractorRef { - type ExtractorRef<'a> where Self: 'a; - fn extractor_ref(&self) -> Self::ExtractorRef<'_>; -} - -pub trait HasExtractorMut { - type ExtractorMut<'a> where Self: 'a; - fn extractor_mut(&mut self) -> Self::ExtractorMut<'_>; -} -``` - -`ExtractField` is the extraction operation. It attempts to read the variant named by `Tag` out of the extractor, returning `Ok(value)` if the runtime value is that variant or `Err(remainder)` if it is not, where the remainder is the same extractor with that one variant ruled out: - -```rust -pub trait ExtractField { - type Value; - type Remainder; - fn extract_field(self, _tag: PhantomData) -> Result; -} -``` - -`FinalizeExtract` discharges a remainder that has become uninhabited. Its method returns *any* type, which is sound precisely because there is no value to return it from — it is implemented for the empty [`Void`](../types/either.md) type and for the standard-library `Infallible`, both by matching on the empty value: - -```rust -pub trait FinalizeExtract { - fn finalize_extract(self) -> T; -} - -impl FinalizeExtract for Void { - fn finalize_extract(self) -> T { match self {} } -} - -impl FinalizeExtract for Infallible { - fn finalize_extract(self) -> T { match self {} } -} -``` - -`FinalizeExtractResult` is the convenience wrapper that collapses the `Result` produced by the last extraction. It is implemented for any `Result` whose error half implements `FinalizeExtract`, returning the `Ok` value and discharging the `Err`: - -```rust -pub trait FinalizeExtractResult { - type Output; - fn finalize_extract_result(self) -> Self::Output; -} - -impl FinalizeExtractResult for Result -where E: FinalizeExtract { - type Output = T; - fn finalize_extract_result(self) -> T { - match self { - Ok(value) => value, - Err(remainder) => remainder.finalize_extract(), - } - } -} -``` - -## Behavior - -Extraction proceeds variant by variant against a shrinking remainder until that remainder is uninhabited. The derive generates a partial companion enum with one [`MapType`](map_type.md) parameter per variant; the marker in each position decides whether that variant's payload is present (`IsPresent`) or has been mapped to the empty `Void` type (`IsVoid`). `to_extractor` starts the chain at the all-`IsPresent` configuration, where every variant is still possible. Each `extract_field` call is available only when the requested variant's marker is `IsPresent`; it matches on the value, returning the payload if it is that variant, or returning the remainder with that one marker flipped to `IsVoid` if it is not. - -Exhaustiveness is proven at the type level rather than by a wildcard. As variants are ruled out, the remainder's markers turn to `IsVoid` one by one, and `IsVoid` maps each payload slot to `Void`. When every marker is `IsVoid`, every arm of the partial enum holds a `Void`, so the whole type is uninhabited. The derive supplies a `FinalizeExtract` impl on exactly that all-`IsVoid` configuration, and that impl is sound only because the value cannot exist — `match self {}` has no arms to write. A caller therefore reaches `finalize_extract` (directly, or through `finalize_extract_result` on the last `Result`) only after trying every variant, and the compiler accepts the discharge with no wildcard arm. - -The three accessor traits differ only in ownership. `HasExtractor` consumes the value and yields an owned extractor whose payloads are owned; the `from_extractor` method reverses `to_extractor` for an unmatched value. `HasExtractorRef` and `HasExtractorMut` borrow the value and yield a borrowed extractor over the same partial enum, carrying a `MapTypeRef` marker (`IsRef` or `IsMut`) that maps each payload slot to a shared or mutable reference, so a value can be matched without being moved. - -## Examples - -The family is normally driven through `to_extractor`, a chain of `extract_field` calls, and `finalize_extract_result` to discharge the impossible remainder at the end: - -```rust -use cgp::prelude::*; -use cgp::core::field::traits::FinalizeExtractResult; - -#[derive(ExtractField)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} - -fn area(shape: Shape) -> f64 { - match shape.to_extractor().extract_field(PhantomData::) { - Ok(circle) => core::f64::consts::PI * circle.radius * circle.radius, - Err(remainder) => { - // remainder now has Circle ruled out (IsVoid) - let rect = remainder - .extract_field(PhantomData::) - .finalize_extract_result(); // remainder is now uninhabited; cannot fail - rect.width * rect.height - } - } -} -``` - -After the second extraction the remainder has both markers `IsVoid`, so its type is uninhabited and `finalize_extract_result` is accepted with no wildcard arm. Adding a third variant to `Shape` would make this function fail to compile until the new variant is handled, because the remainder after two extractions would no longer be uninhabited. - -## Related constructs - -The enum-side derive that generates the partial enums and all these impls is [`#[derive(ExtractField)]`](../derives/derive_extract_field.md), whose doc shows the exact expanded code. The presence markers `IsPresent` and `IsVoid` are [`MapType`](map_type.md) implementations, and the empty remainder bottoms out in the [`Void`](../types/either.md) type that anchors the sum spine. The construction counterpart, which puts a variant *into* an enum rather than taking one out, is [`FromVariant`](from_variant.md). The struct analogue of the whole family is the builder family in [`has_builder`](has_builder.md), which assembles a record field by field instead of deconstructing a variant. The conceptual overview that frames this family is [extensible variants](../../concepts/extensible-variants.md), worked through in the [expression interpreter](../../examples/expression-interpreter.md) example. - -## Source - -- The traits — `ExtractField`, `HasExtractor`, `HasExtractorRef`, `HasExtractorMut`, `FinalizeExtract`, and `FinalizeExtractResult` — are all defined in [crates/core/cgp-field/src/traits/extract_field.rs](../../../crates/core/cgp-field/src/traits/extract_field.rs), with `PartialData` in [partial_data.rs](../../../crates/core/cgp-field/src/traits/partial_data.rs). -- The `MapType` markers are in [crates/core/cgp-field/src/impls/map_type.rs](../../../crates/core/cgp-field/src/impls/map_type.rs) and the `MapTypeRef` markers in [map_type_ref.rs](../../../crates/core/cgp-field/src/impls/map_type_ref.rs). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/derive_extract_field.md](../../implementation/entrypoints/derive_extract_field.md). diff --git a/docs/reference/traits/from_variant.md b/docs/reference/traits/from_variant.md deleted file mode 100644 index ad764a71..00000000 --- a/docs/reference/traits/from_variant.md +++ /dev/null @@ -1,76 +0,0 @@ -# `FromVariant` - -`FromVariant` constructs an enum from a single named variant, with the variant chosen by a type-level tag rather than by writing the concrete variant constructor. - -## Purpose - -`FromVariant` solves the problem of building an enum value in generic code that does not — and cannot — name the concrete variant constructor. Writing `Shape::Circle(circle)` hard-codes both the enum and the variant, so it only works where both are known statically. `FromVariant` instead selects the variant with a type-level [`Symbol!`](../macros/symbol.md) tag, so a provider parameterized over the tag can construct whichever variant it was asked to build, on whatever enum implements the trait for that tag. It is the construction counterpart to the extractor's deconstruction: where [`ExtractField`](extract_field.md) takes one variant *out* of a value, `FromVariant` puts one variant *in*. - -This is the smallest trait of the extensible-variant family. It carries no partial companion type and no presence tracking — there is no state to track when building a single variant — so it is just a direct, tag-selected constructor. It is implemented for an enum by [`#[derive(FromVariant)]`](../derives/derive_from_variant.md), which emits one impl per variant, and it is the construction half that the casts and dispatchers reach for after a match has decided which variant to build. - -## Definition - -`FromVariant` is parameterized by the variant's tag and exposes the variant's payload type as an associated `Value`: - -```rust -pub trait FromVariant { - type Value; - fn from_variant(_tag: PhantomData, value: Self::Value) -> Self; -} -``` - -`Tag` is the variant's name as a type-level string `Symbol!`, `Value` is that variant's payload type, and `from_variant` wraps a payload into the enum as the chosen variant. The `PhantomData` argument carries no data; its sole job is to let the caller select which variant to build when several `FromVariant` impls — one per variant — are in scope on the same enum. The trait is implemented once per variant, each impl fixing its own `Tag` and `Value`, so the choice of impl *is* the choice of variant. - -## Behavior - -Each `FromVariant` impl is a thin wrapper that maps a payload to its variant. The derive emits, for every single-payload variant, an impl keyed by that variant's name symbol with the payload as `Value`, whose `from_variant` simply returns `Self::Variant(value)`. There is no intermediate type, no `MapType` marker, and no validation beyond the type system's own check that the supplied `value` matches the variant's payload type. Because the impls are distinguished only by their `Tag` type parameter, resolving a `from_variant` call comes down to which `Symbol!` the caller names in the `PhantomData` argument — the compiler picks the matching impl and inlines it to the corresponding constructor. - -For an enum `Shape { Circle(Circle), Rectangle(Rectangle) }`, the derive produces: - -```rust -impl FromVariant for Shape { - type Value = Circle; - fn from_variant(_tag: PhantomData, value: Circle) -> Self { - Self::Circle(value) - } -} - -impl FromVariant for Shape { - type Value = Rectangle; - fn from_variant(_tag: PhantomData, value: Rectangle) -> Self { - Self::Rectangle(value) - } -} -``` - -A call to `Shape::from_variant(PhantomData::, circle)` resolves to the first impl and is exactly `Shape::Circle(circle)`. The benefit appears only in generic code: a function that is itself parameterized over a tag `Tag` with a `C: FromVariant` bound can build the variant named by `Tag` without ever mentioning a concrete variant. - -## Examples - -`FromVariant` lifts a value into an enum by naming the variant with a tag, which a concrete call site can do directly and generic code can do over an abstract `Tag`: - -```rust -use cgp::prelude::*; - -#[derive(FromVariant)] -pub enum Shape { - Circle(Circle), - Rectangle(Rectangle), -} - -fn wrap_circle(circle: Circle) -> Shape { - Shape::from_variant(PhantomData::, circle) -} -``` - -The call is equivalent to `Shape::Circle(circle)`, but because the variant is selected by the type-level tag, the same construction pattern works inside code that is generic over the tag and the enum. - -## Related constructs - -The derive that generates the per-variant impls is [`#[derive(FromVariant)]`](../derives/derive_from_variant.md), whose doc shows the exact expanded code; it is also folded into [`#[derive(CgpVariant)]`](../derives/derive_cgp_variant.md) and [`#[derive(CgpData)]`](../derives/derive_cgp_data.md). The reverse operation is [`ExtractField`](extract_field.md), which deconstructs an enum variant by variant rather than constructing one. The variant tag is a [`Symbol!`](../macros/symbol.md) type-level string, the same kind of tag that keys the field traits. For structs, the analogous field-setting building block is the builder family in [`has_builder`](has_builder.md). The conceptual overview that frames variant construction is [extensible variants](../../concepts/extensible-variants.md), worked through in the [expression interpreter](../../examples/expression-interpreter.md) example, where upcasting a small local enum relies on `FromVariant` to rebuild each variant into the target. - -## Source - -- The `FromVariant` trait is defined in [crates/core/cgp-field/src/traits/from_variant.rs](../../../crates/core/cgp-field/src/traits/from_variant.rs). -- The per-variant impls are generated by `derive_from_variant` in [crates/macros/cgp-macro-lib/src/derive_from_variant.rs](../../../crates/macros/cgp-macro-lib/src/derive_from_variant.rs), driving `derive_from_variant_from_enum` in [crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_from_variant.rs). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/derive_from_variant.md](../../implementation/entrypoints/derive_from_variant.md). diff --git a/docs/reference/traits/has_builder.md b/docs/reference/traits/has_builder.md deleted file mode 100644 index 7d48c1bb..00000000 --- a/docs/reference/traits/has_builder.md +++ /dev/null @@ -1,122 +0,0 @@ -# `HasBuilder` and the builder trait family - -The builder family is the set of traits that let a record be assembled one field at a time, with field presence tracked at the type level so that a value can be finalized only once every field is set. - -## Purpose - -The builder family solves the problem of constructing a record incrementally and generically, where the fields are not all known at the same place in the code and the construction must still be checked at compile time. A plain struct literal requires every field to be supplied at once; these traits instead start from an empty *partial* value and add fields to it one by one, with each addition advancing a type-level record of which fields are present. The payoff is that finalizing an incomplete value is a compile error, not a runtime panic — the trait that turns a partial value back into the concrete struct is implemented only for the fully-present configuration. - -The family is built around a single primitive, `UpdateField`, that changes one field's storage from one state to another. Everything else is either a wrapper over that primitive (`BuildField` sets an absent field present, `TakeField` removes a present field) or an entry/exit point for the whole process (`HasBuilder`/`IntoBuilder` start a build, `FinalizeBuild` ends one). The traits live in the field crate and are implemented for a struct by [`#[derive(BuildField)]`](../derives/derive_build_field.md), which also generates the partial companion type they operate on. - -## Definition - -The family divides into entry points, the update primitive, two wrappers over it, and the finalize step. The entry points obtain a partial value to build into. `HasBuilder` produces an empty one and `IntoBuilder` produces a fully-present one from an existing value: - -```rust -pub trait HasBuilder { - type Builder; - fn builder() -> Self::Builder; -} - -pub trait IntoBuilder { - type Builder; - fn into_builder(self) -> Self::Builder; -} -``` - -`UpdateField` is the primitive every field operation reduces to. It changes the field named by `Tag` from its current marker (`Mapper`) to the new marker `M`, both of which are [`MapType`](map_type.md) markers, and returns the field's old value alongside the rebuilt partial value: - -```rust -pub trait UpdateField { - type Value; - type Mapper: MapType; // the field's marker before the update - type Output; // the partial value with the field now in state M - fn update_field( - self, - _tag: PhantomData, - value: M::Map, - ) -> (::Map, Self::Output); -} -``` - -The `M::Map` argument and the `Mapper::Map` first return component are the field's value as it is *stored* under each marker: `IsPresent` stores the value itself, `IsNothing` stores `()`. So updating an absent field to present takes the real value in and returns `()` as the old value; the reverse takes `()` in and returns the real value. - -`BuildField` and `TakeField` are the two directions of that transition, each defined once in the field crate as a blanket impl over `UpdateField`. `BuildField` is the `IsNothing → IsPresent` direction — set a currently-absent field — and `TakeField` is the `IsPresent → IsNothing` direction — remove a currently-present field: - -```rust -pub trait BuildField { - type Value; - type Output; - fn build_field(self, _tag: PhantomData, value: Self::Value) -> Self::Output; -} - -impl BuildField for Context -where - Context: UpdateField, -{ /* build_field = self.update_field(tag, value).1 */ } - -pub trait TakeField { - type Value; - type Remainder; - fn take_field(self, _tag: PhantomData) -> (Self::Value, Self::Remainder); -} - -impl TakeField for Context -where - Context: UpdateField, -{ /* take_field = self.update_field(tag, ()) */ } -``` - -`PartialData` records which concrete struct a partial value targets, and `FinalizeBuild` — a subtrait of `PartialData` — turns the partial value back into that struct: - -```rust -pub trait PartialData { - type Target; -} - -pub trait FinalizeBuild: PartialData { - fn finalize_build(self) -> Self::Target; -} -``` - -## Behavior - -A build is a sequence of `UpdateField`-driven state changes that only finalizes at the all-present configuration. The entry point fixes the starting state: `HasBuilder::builder()` returns the partial type with every field marker `IsNothing` (an empty value where each field is stored as `()`), while `IntoBuilder::into_builder(self)` returns it with every marker `IsPresent` (a full value carrying the real fields). Each `build_field` call flips one marker from `IsNothing` to `IsPresent` by calling the generated `update_field` and keeping only its `Output`; each `take_field` does the reverse and also hands back the removed value. - -Presence lives entirely in the type. The derive generates the partial struct with one `MapType` parameter per field, and the marker in each position decides whether that field's slot holds the value (`IsPresent`), holds `()` (`IsNothing`), or holds the empty `Void` type (`IsVoid`). Because `BuildField` requires `Mapper = IsNothing` and `TakeField` requires `Mapper = IsPresent`, the compiler rejects building a field that is already set or taking one that is absent. The derive also emits a [`HasField`](has_field.md) impl on the partial type gated on `IsPresent`, so a field that has been set can be read back out of a still-incomplete value. - -Finalizing is what makes the tracking load-bearing. The derive provides exactly one `FinalizeBuild` impl, on the all-`IsPresent` configuration of the partial type, so `finalize_build` is in scope only when every field is present; calling it on a partial value with any `IsNothing` field fails to compile. `PartialData::Target` is implemented for *every* configuration and names the struct being built, which is how generic builder code knows the destination type before the build is complete. - -## Examples - -The family is normally driven through `builder()`, a series of `build_field` calls, and `finalize_build`, with `build_from` (from the field crate's `CanBuildFrom`, itself layered on `BuildField`) copying every shared field from another record in one step: - -```rust -use cgp::prelude::*; -use cgp::core::field::impls::CanBuildFrom; - -#[derive(BuildField)] -pub struct FooBar { pub foo: u64, pub bar: String } - -#[derive(BuildField)] -pub struct FooBarBaz { pub foo: u64, pub bar: String, pub baz: bool } - -fn extend(foo_bar: FooBar) -> FooBarBaz { - FooBarBaz::builder() // all IsNothing - .build_from(foo_bar) // foo, bar now IsPresent - .build_field(PhantomData::, true) // baz now IsPresent - .finalize_build() // only the all-present impl applies -} -``` - -Each line changes the partial type, and the `finalize_build` on the last line type-checks only because every marker has reached `IsPresent` by that point. Reordering the steps so that `finalize_build` ran before `baz` was set would be a compile error rather than a runtime failure. - -## Related constructs - -The struct-side derive that generates the partial type and all these impls is [`#[derive(BuildField)]`](../derives/derive_build_field.md), whose doc shows the exact expanded code. The presence markers `IsPresent`, `IsNothing`, and `IsVoid` are [`MapType`](map_type.md) implementations, and the partial type's `MapType` parameters are what record per-field state. Fields already set on a partial value are read back through [`HasField`](has_field.md). The enum counterparts to this family are the extractor traits in [`extract_field`](extract_field.md), which deconstruct a value variant by variant, and [`FromVariant`](from_variant.md), which constructs an enum from a single variant. The conceptual overview that ties this family into the [extensible builder pattern](../../concepts/extensible-records.md) is in [extensible records](../../concepts/extensible-records.md), worked through in the [application builder](../../examples/application-builder.md) example. - -## Source - -- The traits are defined in [crates/core/cgp-field/src/traits/has_builder.rs](../../../crates/core/cgp-field/src/traits/has_builder.rs) (`HasBuilder`, `IntoBuilder`), [build_field.rs](../../../crates/core/cgp-field/src/traits/build_field.rs) (`BuildField`, `FinalizeBuild`), [update_field.rs](../../../crates/core/cgp-field/src/traits/update_field.rs) (`UpdateField`), [take_field.rs](../../../crates/core/cgp-field/src/traits/take_field.rs) (`TakeField`), and [partial_data.rs](../../../crates/core/cgp-field/src/traits/partial_data.rs) (`PartialData`). -- The `MapType` markers are in [crates/core/cgp-field/src/impls/map_type.rs](../../../crates/core/cgp-field/src/impls/map_type.rs), and `CanBuildFrom` in [crates/core/cgp-field/src/impls/build_from.rs](../../../crates/core/cgp-field/src/impls/build_from.rs). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/derive_build_field.md](../../implementation/entrypoints/derive_build_field.md). diff --git a/docs/reference/traits/has_field.md b/docs/reference/traits/has_field.md deleted file mode 100644 index 834570af..00000000 --- a/docs/reference/traits/has_field.md +++ /dev/null @@ -1,134 +0,0 @@ -# `HasField` - -`HasField` is the consumer trait for reading a single named field out of a context by a type-level tag, with `HasFieldMut` adding mutable access, the provider-side mirrors `FieldGetter` and `MutFieldGetter` supplying the same capability through CGP wiring, and the lifetime helpers `MapField`/`FieldMapper` letting chained field accesses borrow correctly. - -## Purpose - -`HasField` exists so that a provider can demand a specific value from its context without naming the context's concrete type. The recurring problem in CGP is that a provider is generic over the context but still needs a `name`, a `port`, or some other field out of it; it cannot reach into a struct it does not know. `HasField` solves this by keying each field with a *tag type* that stands in for the field's name, so a provider can write `Context: HasField` in its `where` clause and receive the field through the trait system. This makes field access an impl-side dependency rather than part of any public interface — any context that supplies a matching field satisfies the bound automatically. See [impl-side dependencies](../../concepts/impl-side-dependencies.md) for why this constraint-based style is the heart of CGP. - -The trait is deliberately tiny because it is the foundation that the value-injection macros stand on. [`#[derive(HasField)]`](../derives/derive_has_field.md) generates the impls from a struct's fields, and higher-level constructs — `#[cgp_auto_getter]`, `#[cgp_getter]` through the [`UseField`](../providers/use_field.md) provider, and `#[implicit]` arguments — all desugar into `HasField` bounds and `get_field` calls. Understanding `HasField` is understanding how every one of those reaches a field. - -## Definition - -`HasField` carries the field's type as an associated `Value` and returns a reference to it, taking a `PhantomData` argument that exists only to disambiguate which field is meant when several `HasField` impls are in scope: - -```rust -pub trait HasField { - type Value; - - fn get_field(&self, _tag: PhantomData) -> &Self::Value; -} -``` - -The `Tag` parameter is a type-level name. A named struct field is keyed by [`Symbol!("field_name")`](../macros/symbol.md), the type-level string of its identifier; a tuple field is keyed by [`Index`](../types/index.md), the type-level natural number of its position. Because the tag is a type and not a value, `get_field` receives `PhantomData` purely to tell the compiler which impl to select. - -`HasFieldMut` is the mutable extension. It supertraits `HasField` and adds a method returning `&mut Self::Value`: - -```rust -pub trait HasFieldMut: HasField { - fn get_field_mut(&mut self, tag: PhantomData) -> &mut Self::Value; -} -``` - -Both traits carry `#[diagnostic::on_unimplemented]` notes that point a reader at `#[derive(HasField)]` when the bound is unsatisfied, so a missing field surfaces as a readable error rather than an opaque trait failure. - -The consumer side has a provider-side mirror so field access can be wired like any other component rather than only implemented directly on the context. `FieldGetter` is the provider trait corresponding to `HasField`: instead of `&self`, it takes the context as an explicit argument, which is the shape CGP providers use: - -```rust -pub trait FieldGetter { - type Value; - - fn get_field(context: &Context, _tag: PhantomData) -> &Self::Value; -} - -pub trait MutFieldGetter: FieldGetter { - fn get_field_mut(context: &mut Context, tag: PhantomData) -> &mut Self::Value; -} -``` - -Alongside these, `MapField` and `FieldMapper` add a borrow-through-a-closure variant of the same access. They exist to organize lifetime inference: chaining `context.get_field().get_field()` would otherwise force `Self::Value` to be `'static`, so `map_field` takes a `for<'a> FnOnce(&'a Self::Value) -> &'a T` closure and applies it to the borrowed field, letting the compiler infer the correct lifetime: - -```rust -pub trait MapField: HasField { - fn map_field( - &self, - _tag: PhantomData, - mapper: impl for<'a> FnOnce(&'a Self::Value) -> &'a T, - ) -> &T; -} - -pub trait FieldMapper: FieldGetter { - fn map_field( - context: &Context, - _tag: PhantomData, - mapper: impl for<'a> FnOnce(&'a Self::Value) -> &'a T, - ) -> &T; -} -``` - -## Behavior - -The consumer impls of `HasField` come almost entirely from `#[derive(HasField)]`; the trait file itself provides only the blanket impls that make the access compose. The first is a `Deref` forwarding impl: when a context dereferences to a target that has the field, the context inherits it, so a `HasField` bound passes transparently through smart-pointer wrappers. This impl is marked `#[diagnostic::do_not_recommend]` so the compiler does not suggest the blanket path in error messages. `HasFieldMut` carries the analogous `DerefMut` forwarding impl, with the target additionally bounded `'static`. - -The provider side is what connects field access to wiring. `UseContext` implements `FieldGetter` for any context that itself has the field, delegating straight to `context.get_field(...)`: - -```rust -impl FieldGetter for UseContext -where - Context: HasField, -{ - type Value = Field; - fn get_field(context: &Context, _tag: PhantomData) -> &Self::Value { - context.get_field(PhantomData) - } -} -``` - -`FieldMapper` is implemented as a blanket impl for any `FieldGetter` (with the getter and tag `'static`), so every provider-side getter automatically gains the lifetime-friendly `map_field`. Likewise `MapField` is a blanket impl for every `HasField` whose tag is `'static`. The split between the two sides is the standard CGP consumer/provider duality: `HasField`/`HasFieldMut` are what generic code bounds against, while `FieldGetter`/`MutFieldGetter` are what gets wired — the [`UseField`](../providers/use_field.md) provider is the wiring-side implementation that `#[cgp_getter]` targets. - -## Examples - -A provider that needs a value from its context expresses the need as a `HasField` bound and reads the field with `get_field`: - -```rust -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasField, -{ - fn greet(&self) { - println!("Hello, {}!", self.get_field(PhantomData)); - } -} - -#[derive(HasField)] -pub struct Person { - pub name: String, -} - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} -``` - -Because `Person` derives `HasField`, it implements `HasField`, which is exactly the bound `GreetHello` requires; the wiring type-checks and `person.greet()` prints the name. In practice the explicit bound is rarely hand-written — `#[cgp_auto_getter]`, `#[cgp_getter]`, and `#[implicit]` arguments all generate it for you on top of these impls. - -## Related constructs - -`HasField` is generated by [`#[derive(HasField)]`](../derives/derive_has_field.md), which emits one `HasField` and one `HasFieldMut` impl per struct field. Its tags come from [`Symbol!`](../macros/symbol.md) for named fields and [`Index`](../types/index.md) for tuple fields. The provider-side [`UseField`](../providers/use_field.md) provider is the wiring-side implementation of `FieldGetter` that `#[cgp_getter]` targets, and field access in general is the canonical example of [impl-side dependencies](../../concepts/impl-side-dependencies.md). For the whole-struct structural view rather than single-field access, see [`HasFields`](has_fields.md). - -## Source - -- The consumer traits and their blanket impls are in [crates/core/cgp-field/src/traits/has_field.rs](../../../crates/core/cgp-field/src/traits/has_field.rs) (`HasField`, `FieldGetter`, the `Deref` forwarding impl, and the `UseContext` provider impl) and [crates/core/cgp-field/src/traits/has_field_mut.rs](../../../crates/core/cgp-field/src/traits/has_field_mut.rs) (`HasFieldMut`, `MutFieldGetter`, the `DerefMut` forwarding impl). -- The `MapField`/`FieldMapper` lifetime helpers are in [crates/core/cgp-field/src/traits/map_field.rs](../../../crates/core/cgp-field/src/traits/map_field.rs). -- The `UseField` provider lives in [crates/core/cgp-field/src/impls/use_field.rs](../../../crates/core/cgp-field/src/impls/use_field.rs). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/derive_has_field.md](../../implementation/entrypoints/derive_has_field.md). diff --git a/docs/reference/traits/has_fields.md b/docs/reference/traits/has_fields.md deleted file mode 100644 index 971dc0ef..00000000 --- a/docs/reference/traits/has_fields.md +++ /dev/null @@ -1,90 +0,0 @@ -# `HasFields` - -`HasFields` is the consumer trait that exposes a type's complete field structure as a single type-level shape — a [`Product`](../macros/product.md) for a struct, a [`Sum`](../macros/sum.md) for an enum — with `HasFieldsRef` giving the borrowed view and `ToFields`, `FromFields`, and `ToFieldsRef` providing the round-trip conversions between a concrete value and that shape. - -## Purpose - -`HasFields` exists to describe an entire type's shape as one type rather than one entry per field. Where [`HasField`](has_field.md) answers "give me *this* field by name," `HasFields` answers "describe *all* the fields at once": it produces a single associated `Fields` type that is the type-level list of every field, each tagged with its name. This aggregate view is what lets generic code fold over a context's shape uniformly — serialization, builders, conversions, and the extensible-record and extensible-variant machinery all operate on the `Fields` type rather than reaching for fields one at a time. - -The distinction from `HasField` is the whole point. `HasField` is indexed access through many small impls, used for dependency injection where a provider wants one specific value. `HasFields` is the structural mirror — a single impl whose `Fields` type enumerates the full shape, used when an algorithm must process the type as a record or as a tagged union. The two are complementary, and a type that wants both indexed access and structural processing derives both with [`#[derive(HasField, HasFields)]`](../derives/derive_has_fields.md). - -The conversion traits make that structural view a two-way door. `ToFields` turns an owned value into its `Fields` shape, `FromFields` rebuilds the value from the shape, and `ToFieldsRef` borrows the value as a shape of references. Together they let generic code decompose a concrete type into its anonymous structural form, transform it, and reconstruct the concrete type. - -## Definition - -The two core traits each carry a single associated type and nothing else. `HasFields` names the owned shape, and `HasFieldsRef` names the borrowed shape, parameterized by a lifetime: - -```rust -pub trait HasFields { - type Fields; -} - -pub trait HasFieldsRef { - type FieldsRef<'a> - where - Self: 'a; -} -``` - -For a struct, `Fields` is a [`Product`](../macros/product.md) — a `Cons`/`Nil` chain of [`Field`](../types/field.md) entries, each value carrying its type-level name tag. For an enum, `Fields` is a [`Sum`](../macros/sum.md) — an `Either`/`Void` chain whose arms are `Field` entries naming each variant and carrying that variant's own fields as a nested product. Named fields and variants are tagged by [`Symbol!`](../macros/symbol.md); tuple fields by `Index`. `FieldsRef<'a>` is the same shape with each value borrowed for `'a`. - -The three conversion traits each supertrait one of the two shape traits and add a single method. `ToFields` and `FromFields` build on `HasFields`, while `ToFieldsRef` builds on `HasFieldsRef`: - -```rust -pub trait ToFields: HasFields { - fn to_fields(self) -> Self::Fields; -} - -pub trait FromFields: HasFields { - fn from_fields(fields: Self::Fields) -> Self; -} - -pub trait ToFieldsRef: HasFieldsRef { - fn to_fields_ref<'a>(&'a self) -> Self::FieldsRef<'a> - where - Self: 'a; -} -``` - -`to_fields` consumes the value to produce the owned product or sum, `from_fields` consumes the shape to reconstruct the value, and `to_fields_ref` borrows the value to produce a shape of references — the non-consuming counterpart used when the original value must be kept. - -## Behavior - -All five impls come from [`#[derive(HasFields)]`](../derives/derive_has_fields.md); the trait module defines only the bare trait shapes. The derive dispatches on whether the type is a struct or an enum: a struct produces a `Product!` of its fields, an enum produces a `Sum!` of its variants, and a single-field tuple struct (a newtype) is treated specially so its `Fields` is the inner type directly rather than a one-element product. - -The conversions are mechanical inverses of one another. `to_fields` folds the concrete value into a `Cons` chain (or an `Either` arm for an enum), `from_fields` pattern-matches that chain back into the concrete fields, and `to_fields_ref` builds the same `Cons` chain over borrows. Because `from_fields` and `to_fields` round-trip through the identical `Fields` type, generic code can decompose a value, operate on the structural form, and rebuild the value with the guarantee that the shapes line up by construction. - -## Examples - -Deriving `HasFields` lets generic code treat any type as a record without naming its concrete type, and a value can be round-tripped through its `Fields` shape: - -```rust -use cgp::prelude::*; - -#[derive(HasField, HasFields)] -pub struct Config { - pub host: String, - pub port: u16, -} - -let config = Config { host: "localhost".into(), port: 8080 }; - -// ToFields: Config -> Product![Field, Field] -let fields = config.to_fields(); - -// FromFields: the product -> back to Config -let config_again = Config::from_fields(fields); -``` - -For an enum the `Fields` shape is a sum instead of a product. A `Shape` enum with `Circle { radius: f64 }` and `Rectangle { width: f64, height: f64 }` variants produces a `Fields` of `Either, Either, Void>>`, and the same `to_fields`/`from_fields` pair moves a `Shape` value in and out of that sum. Generic algorithms bound `Context: HasFields` (or `ToFields`/`FromFields`) and process `Context::Fields` structurally, which is how the extensible-data machinery operates over arbitrary contexts. - -## Related constructs - -`HasFields` is the structural counterpart to [`HasField`](has_field.md): `HasField` gives indexed, single-field access for dependency injection, while `HasFields` gives the whole-shape view. All five impls are generated by [`#[derive(HasFields)]`](../derives/derive_has_fields.md). The `Fields` shape is built from [`Product`](../macros/product.md) for structs and [`Sum`](../macros/sum.md) for enums, with each entry a [`Field`](../types/field.md) tagged by [`Symbol!`](../macros/symbol.md). - -## Source - -- The trait definitions are in [crates/core/cgp-field/src/traits/has_fields.rs](../../../crates/core/cgp-field/src/traits/has_fields.rs) (`HasFields`, `HasFieldsRef`), [crates/core/cgp-field/src/traits/to_fields.rs](../../../crates/core/cgp-field/src/traits/to_fields.rs) (`ToFields`, `ToFieldsRef`), and [crates/core/cgp-field/src/traits/from_fields.rs](../../../crates/core/cgp-field/src/traits/from_fields.rs) (`FromFields`). -- The `Field`, `Cons`/`Nil`, `Either`/`Void` building blocks live under [crates/core/cgp-field/src/types/](../../../crates/core/cgp-field/src/types/). -- The derive that emits the impls is in [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/derive_has_fields.md](../../implementation/entrypoints/derive_has_fields.md). diff --git a/docs/reference/traits/is_provider_for.md b/docs/reference/traits/is_provider_for.md deleted file mode 100644 index 783e51a4..00000000 --- a/docs/reference/traits/is_provider_for.md +++ /dev/null @@ -1,108 +0,0 @@ -# `IsProviderFor` - -`IsProviderFor` is the marker trait that every CGP provider trait carries as a supertrait, propagating a provider's `where`-bounds so that an unmet dependency surfaces as a readable compiler error instead of being silently hidden. - -## Purpose - -`IsProviderFor` exists to make missing dependencies diagnosable. A CGP provider implements its provider trait under a `where` clause that lists everything it needs from the context — a getter, an abstract type, another component. When that clause is unmet, the natural question "does this provider implement the provider trait?" produces a frustrating answer: Rust reports only that the trait is not implemented, with no explanation, because a competing candidate (the provider blanket impl from [`#[cgp_component]`](../macros/cgp_component.md)) is also in scope and Rust suppresses the detailed reasoning whenever more than one impl could apply. The real cause — a single absent field or type — stays buried. - -`IsProviderFor` is the second, independent path that un-hides those errors. It is an empty marker trait, trivially satisfiable in principle, but the macros implement it for a provider under *exactly the same* `where` bounds as the provider trait itself. Because every generated provider trait names `IsProviderFor` as a supertrait, asking whether a provider satisfies `IsProviderFor` forces the compiler to evaluate those bounds — and since that question has only one candidate impl (the explicit one the macro wrote, not a blanket), Rust does not hide its reasoning and reports the precise unsatisfied constraint. The trait carries no behavior; its entire value is making the dependency set visible to the compiler along a path Rust will explain. - -This is why the dedicated, full treatment lives here while other documents mention `IsProviderFor` only in passing. To a user writing components and providers, it is invisible plumbing generated and consumed by the macros. To anyone reading a wiring error or building higher-order providers, it is the mechanism that turns an opaque failure into a pointer at the actual gap. - -The phenomenon it works around is specific to how Rust prunes diagnostics. When two impls could satisfy a bound — here, the provider blanket impl that routes through the delegation table, and the user's explicit provider impl — Rust reports only that no impl matched, withholding the per-impl reasons because it cannot know which candidate the programmer intended. `IsProviderFor` sidesteps the ambiguity by being implemented along a single, explicit path with no competing blanket, so the compiler commits to that path and prints why its `where` clause failed. - -## Definition - -`IsProviderFor` is an empty trait parameterized by a component, a context, and a parameter tuple: - -```rust -#[diagnostic::on_unimplemented( - note = "You need to add `#[cgp_provider({Component})]` on the impl block for CGP provider traits" -)] -pub trait IsProviderFor {} -``` - -The three parameters identify which provider trait implementation is being asserted. `Component` is the component-name type that corresponds to the provider trait, the same key used in the delegation table. `Context` is the context type the provider trait is implemented for. `Params` collects any additional generic parameters of the provider trait, grouped into a tuple when there is more than one and defaulting to the unit tuple `()` when there are none — so a provider trait with parameters `` becomes `Params = (I, J)`, and a parameterless one uses the default. `Self` is the provider type whose validity is being asserted. The `#[diagnostic::on_unimplemented]` note nudges a reader who hits the error toward the missing `#[cgp_provider]` attribute that would generate the impl. - -## Behavior - -`IsProviderFor` is generated, never hand-written, and it appears in three places that together form the diagnostic chain. First, [`#[cgp_component]`](../macros/cgp_component.md) emits every provider trait with `IsProviderFor` as a supertrait, so that for a component `Foo` the provider trait reads `pub trait FooGetterAt: IsProviderFor`. This supertrait link is what binds a provider's dependency set to the marker. - -Second, [`#[cgp_provider]`](../macros/cgp_provider.md) and [`#[cgp_impl]`](../macros/cgp_impl.md) emit, beside the provider trait impl, an `IsProviderFor` impl for the same provider type under the same `where` clause. A provider implemented as `impl FooGetterAt for GetFooValue where Context: HasField` gains a matching empty impl `impl IsProviderFor for GetFooValue where Context: HasField {}`. The two impls carry identical bounds, so the marker is satisfiable exactly when the provider trait is. - -Third, [`delegate_components!`](../macros/delegate_components.md) propagates the marker through the delegation table. For each entry it emits an `IsProviderFor` impl on the table type that forwards to the delegated provider's own `IsProviderFor`, generic over context and params: - -```rust -impl IsProviderFor for MyAppComponents -where - GetFooValue: IsProviderFor, -{} -``` - -This forwarding is the crucial step. Because it is an explicit impl rather than a blanket, the compiler follows it and surfaces every unsatisfied constraint coming from `GetFooValue` — the table re-exposes the provider's real requirements at the point of lookup. The forwarding is also what carries dependencies across layers: when an [aggregate provider](../../concepts/aggregate-providers.md) delegates to another bundle, each bundle's `IsProviderFor` impl forwards to the next, so a requirement unmet several tables deep still propagates back to where the component is checked. The generic parameters are literally named `Context` and `Params` in the emitted code. The `Params` slot is filled by whatever a check supplies: a single parameter goes in directly, multiple parameters as a tuple, and a parameterless component uses `()`. - -The supertrait link on the provider trait is the piece that ties this together for the consumer side. A provider trait generated from `#[cgp_component(FooGetterAt)]` for a component `CanGetFooAt` reads as `pub trait FooGetterAt: IsProviderFor`, so any attempt to use the provider trait must first establish `IsProviderFor`. That is why probing `IsProviderFor` is equivalent to probing the provider trait's dependency set, and why the marker can stand in for the provider trait whenever a readable error is wanted. - -## Examples - -`IsProviderFor` is something you observe rather than invoke. A provider that depends on a field gets a matching marker impl automatically: - -```rust -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasField, -{ - fn greet(&self) { - println!("Hello, {}!", self.get_field(PhantomData)); - } -} -``` - -`#[cgp_impl]` emits both the `Greeter` provider trait impl and an `IsProviderFor` impl for `GreetHello`, each guarded by the same `HasField` bound. When this provider is wired onto a context that lacks a `name` field, the table's forwarding `IsProviderFor` impl carries that unmet `HasField` bound back to the point of use — which is exactly what [`check_components!`](../macros/check_components.md) leverages to report the missing field at the wiring site: - -```rust -#[derive(HasField)] -pub struct App { - pub first_name: String, // not `name` -} - -delegate_components! { - App { GreeterComponent: GreetHello } -} - -check_components! { - App { GreeterComponent } -} -``` - -The check forces `App: CanUseComponent`, which routes through `GreetHello: IsProviderFor`, whose `where` clause names the `HasField` bound — so the compiler reports the absent `name` field rather than a bare "provider trait not implemented". - -The marker can also be probed directly, which is what the `#[check_providers(...)]` form of `check_components!` does for a provider that is not the one a context delegates to. Asserting it by hand looks like a where-bound with no body: - -```rust -fn assert_provider() -where - GreetHello: IsProviderFor, -{} -``` - -This holds only if `GreetHello`'s dependencies are met for `App` — the same condition as the provider trait, surfaced along the explicit path. For a component with several type parameters the final argument is the grouping tuple, so a two-parameter component is asserted as `IsProviderFor`. - -## Related constructs - -`IsProviderFor` is the supertrait that [`#[cgp_component]`](../macros/cgp_component.md) attaches to every provider trait, and the empty impl beside it is generated by [`#[cgp_provider]`](../macros/cgp_provider.md) and [`#[cgp_impl]`](../macros/cgp_impl.md). [`delegate_components!`](../macros/delegate_components.md) forwards it through each [`DelegateComponent`](delegate_component.md) entry so a delegated provider's requirements stay visible. [`CanUseComponent`](can_use_component.md) is the context-side counterpart whose blanket impl requires `IsProviderFor` of the delegate, and [`check_components!`](../macros/check_components.md) is the macro that forces both — its `#[check_providers(...)]` form asserts `IsProviderFor` on named providers directly, which is the tool for localizing failures in higher-order provider stacks. - -## Source - -- The trait is defined in [crates/core/cgp-component/src/traits/is_provider.rs](../../../crates/core/cgp-component/src/traits/is_provider.rs) and re-exported through [crates/core/cgp-component/src/macro_prelude.rs](../../../crates/core/cgp-component/src/macro_prelude.rs). -- The provider-trait supertrait link and the per-impl marker impl are emitted by [crates/macros/cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/) and the `#[cgp_provider]`/`#[cgp_impl]` codegen; the table forwarding impl is built in [crates/macros/cgp-macro-core/src/types/delegate_component/mapping/eval.rs](../../../crates/macros/cgp-macro-core/src/types/delegate_component/mapping/eval.rs). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/cgp_component.md](../../implementation/entrypoints/cgp_component.md). diff --git a/docs/reference/traits/map_type.md b/docs/reference/traits/map_type.md deleted file mode 100644 index 4f43addb..00000000 --- a/docs/reference/traits/map_type.md +++ /dev/null @@ -1,113 +0,0 @@ -# `MapType` - -`MapType` is the type-level marker trait whose associated `Map` describes how a single field's value type is wrapped, so that builders and extractors can track each field's presence at the type level rather than at runtime. - -## Purpose - -`MapType` exists to make "what state is this field in?" a question the compiler answers, not the program. CGP's extensible-record machinery represents a half-built struct or a partially-extracted enum as a single type whose every field is independently wrapped: a field that is present carries its value, a field that has been consumed carries nothing, and a field that may or may not be there carries an `Option`. Rather than hard-code those three shapes, CGP parameterizes each field by a `MapType` marker and reads the field's actual storage type out of that marker's `Map`. The marker is the field's state, expressed as a type. - -This is what lets the builder family in [`HasBuilder`](has_builder.md) and the extractor family in [`ExtractField`](extract_field.md) move a record through intermediate states without ever changing the runtime representation of a value. A builder starts every field as "nothing" and flips each one to "present" as it is filled; an extractor starts every variant as "present" and flips each one to "void" as it is consumed. Because the flip is a change of type parameter, the compiler tracks exactly which fields are filled and refuses to finalize a record with a missing field. `MapType` is the vocabulary for those per-field states. - -The companion trait [`MapTypeRef`](#maptyperef) does the same job for borrowed views, where the wrapping additionally introduces a lifetime — a reference, a mutable reference, or an owned value. - -## Definition - -`MapType` is a trait with a single generic associated type. The implementor is a zero-sized marker, and `Map` names the storage type that the marker assigns to a field whose value type is `T`: - -```rust -pub trait MapType { - type Map; -} -``` - -The four standard markers in `cgp-field` cover the states a field passes through. `IsPresent` is the identity wrapping — the field holds its value directly. `IsNothing` erases the value to the unit type, marking the field as absent. `IsVoid` maps to the uninhabited [`Void`](../types/either.md) type, marking a variant that can never be reached. `IsOptional` wraps the value in `Option`, marking a field that is optionally present: - -```rust -impl MapType for IsPresent { type Map = T; } -impl MapType for IsNothing { type Map = (); } -impl MapType for IsVoid { type Map = Void; } -impl MapType for IsOptional { type Map = Option; } -``` - -## `MapTypeRef` - -`MapTypeRef` is the borrowed-view counterpart, used when a record is viewed by reference rather than by value. Its `Map<'a, T>` additionally threads a lifetime, so the marker decides not just whether the value is present but whether it is borrowed shared, borrowed mutably, or owned: - -```rust -pub trait MapTypeRef { - type Map<'a, T: 'a>: 'a; -} -``` - -The three standard markers correspond to the three ways of holding a value behind a lifetime. `IsRef` maps to `&'a T`, `IsMut` to `&'a mut T`, and `IsOwned` to a plain owned `T`: - -```rust -impl MapTypeRef for IsRef { type Map<'a, T: 'a> = &'a T; } -impl MapTypeRef for IsMut { type Map<'a, T: 'a> = &'a mut T; } -impl MapTypeRef for IsOwned { type Map<'a, T: 'a> = T; } -``` - -A borrowed extractor combines the two families: the partial type carries one outer `MapTypeRef` marker (`IsRef` for `extractor_ref`, `IsMut` for `extractor_mut`) shared across all fields, and one `MapType` marker per field. A field's storage type is then `MapType::Map>` — the per-field presence marker applied to the borrowed value type. - -## `TransformMap` and `TransformMapFields` - -`TransformMap` is a value-level natural transformation that converts a field from one `MapType` wrapping to another. Where `MapType` only names storage types, `TransformMap` carries the function that turns an `M1::Map` value into an `M2::Map` value: - -```rust -pub trait TransformMap { - fn transform_mapped(value: M1::Map) -> M2::Map; -} -``` - -`TransformMapFields` lifts such a transform across every field of a whole partial record. It is implemented for any context that is a [`PartialData`](has_builder.md), and it walks the target's [`HasFields`](has_fields.md) product field by field, applying the `Transform` to re-wrap each field from its current marker into `TargetMap`: - -```rust -pub trait TransformMapFields { - type Output; - - fn transform_map_fields(self) -> Self::Output; -} -``` - -The recursion is the load-bearing part. For each `Field` in the spine, `TransformMapFields` uses [`UpdateField`](has_builder.md) twice: it first takes the field out (replacing its marker with `IsNothing`), reads its current marker, applies `Transform::transform_mapped` to convert the value to the `TargetMap` wrapping, then writes it back under `TargetMap`. The result type therefore has every field re-marked to `TargetMap`, with the values transformed accordingly. - -## Examples - -These markers are mostly seen through the generated partial types of [`#[derive(CgpData)]`](../derives/derive_cgp_data.md), but the transform family is directly useful. A common application fills in default values for absent fields by transforming every field's marker to `IsPresent`, supplying `Default::default()` wherever a field was `IsNothing` or an empty `Option`: - -```rust -use cgp::prelude::*; - -pub struct FillDefaults; - -impl TransformMap for FillDefaults { - fn transform_mapped(value: T) -> T { - value - } -} - -impl TransformMap for FillDefaults { - fn transform_mapped(_value: ()) -> T { - T::default() - } -} - -impl TransformMap for FillDefaults { - fn transform_mapped(value: Option) -> T { - value.unwrap_or_default() - } -} -``` - -Applied through `transform_map_fields`, this turns a partially-built record — where some fields are present, some absent, and some optional — into a fully present builder whose missing fields have been filled with their defaults, ready to finalize. - -## Related constructs - -`MapType` is the per-field state vocabulary that [`HasBuilder`](has_builder.md) and its `PartialData`/`UpdateField` family use to track which fields are filled, and that [`ExtractField`](extract_field.md) uses to track which variants are still reachable. The `IsPresent`/`IsNothing`/`IsVoid`/`IsOptional` markers wrap field values; `IsVoid` maps to the uninhabited [`Void`](../types/either.md), the terminator of the [`Either`](../types/either.md) sum spine. The borrowed markers `IsRef`/`IsMut`/`IsOwned` of `MapTypeRef` feed the reference extractors. [`MapFields`](product_ops.md) applies a single `MapType` marker uniformly to every entry of a [`Cons`](../types/cons.md) product or `Either` sum, producing the partial-type field lists these markers populate. The whole scheme is generated by [`#[derive(CgpData)]`](../derives/derive_cgp_data.md). The [optional-field traits](optional_fields.md) build on this vocabulary with the `TransformMapDefault` and `TransformOptional` markers, which fill or wrap fields when finalizing a partially-built record. The conceptual overviews that show these markers tracking field and variant state are [extensible records](../../concepts/extensible-records.md) and [extensible variants](../../concepts/extensible-variants.md). - -## Source - -- `MapType` is defined in [crates/core/cgp-field/src/traits/map_type.rs](../../../crates/core/cgp-field/src/traits/map_type.rs) and `MapTypeRef` in [crates/core/cgp-field/src/traits/map_type_ref.rs](../../../crates/core/cgp-field/src/traits/map_type_ref.rs). -- The standard markers are in [crates/core/cgp-field/src/impls/map_type.rs](../../../crates/core/cgp-field/src/impls/map_type.rs) (`IsPresent`, `IsNothing`, `IsVoid`, `IsOptional`) and [crates/core/cgp-field/src/impls/map_type_ref.rs](../../../crates/core/cgp-field/src/impls/map_type_ref.rs) (`IsRef`, `IsMut`, `IsOwned`). -- `TransformMap` and `TransformMapFields` are in [crates/core/cgp-field/src/traits/transform_map.rs](../../../crates/core/cgp-field/src/traits/transform_map.rs). -- The default-filling transform built on this machinery lives in [crates/extra/cgp-field-extra/src/impls/build_default.rs](../../../crates/extra/cgp-field-extra/src/impls/build_default.rs). diff --git a/docs/reference/traits/monad.md b/docs/reference/traits/monad.md deleted file mode 100644 index 68f8defe..00000000 --- a/docs/reference/traits/monad.md +++ /dev/null @@ -1,72 +0,0 @@ -# Monad traits - -The monad traits — `MonadicTrans`, `MonadicBind`, `LiftValue`, and `ContainsValue` — are the type-level interface that defines what a monad is for [monadic handler composition](../../concepts/monadic-handlers.md): which branch of an output value continues a pipeline, which branch short-circuits, and how values move between those branches. - -## Purpose - -These four traits factor a monad into the distinct capabilities the monadic pipeline machinery needs from it. A monad in CGP is a zero-sized marker type (`IdentMonadic`, `OkMonadic`, `ErrMonadic`, and their transformer forms), and these traits are what give that marker meaning. They are plain capability traits rather than CGP components — they have no generated provider trait or `…Component` marker and are not wired through `delegate_components!`; instead, the [`PipeMonadic`](../providers/monad_providers.md) provider and the per-step `BindOk` / `BindErr` providers consume them as ordinary trait bounds while building a pipeline at compile time. - -The split exists because building a monadic pipeline requires three separable decisions. Composing a list of handlers under a monad needs to know how to turn a continuation provider into a bind step — that is `MonadicBind`. Each bind step needs to know, for a given output type, what the underlying value beneath the monad's wrapper is, and how to put a value back into that output type — that is `ContainsValue` and `LiftValue`. Stacking one monad on top of another needs to apply a monad as a transformer over a base monad — that is `MonadicTrans`. Keeping these as separate traits lets the same marker serve all three roles and lets monads stack by composing their implementations. - -## Definition - -`MonadicBind` maps a continuation provider to the provider that runs one bind step of the monad: - -```rust -pub trait MonadicBind { - type Provider; -} -``` - -The `Provider` parameter is the continuation — the handler that should run on the monad's continue branch — and the `Provider` associated type is the bind provider that wraps it. For the base monads this resolves to a `BindOk` or `BindErr` provider; for `IdentMonadic` it is the continuation unchanged. - -`MonadicTrans` applies a monad as a transformer onto a base monad: - -```rust -pub trait MonadicTrans { - type M; -} -``` - -The `M` parameter is the base monad being transformed, and the `M` associated type is the resulting stacked monad. This is what lets `OkMonadic` be written as `OkMonadicTrans` when a pipeline operates over a nested result type, layering the ok behavior on top of the err behavior. - -`ContainsValue` reads, for a given monadic output type, the value type that sits beneath this monad's wrapper: - -```rust -pub trait ContainsValue { - type Value; -} -``` - -The `Output` parameter is the full output type a step produces; the `Value` associated type is the type carried in the branch the monad threads through, which a continuation handler consumes or which a deeper monad layer unwraps further. - -`LiftValue` moves values into a step's output type, in two directions: - -```rust -pub trait LiftValue { - type Output; - - fn lift_value(value: Value) -> Self::Output; - - fn lift_output(output: Output) -> Self::Output; -} -``` - -The associated `Output` is the final output type of the lift. `lift_value` takes a bare value from the continue or short-circuit branch and wraps it into that output type, and `lift_output` takes a value already in the inner `Output` shape and re-wraps it. The two methods correspond to the two branches a bind step takes: one lifts the short-circuit value back out as the result, the other forwards a continuation's already-computed output. - -## Implementations - -`IdentMonadic` implements all four traits as identities, which is what makes it thread every value forward without ever short-circuiting. `MonadicTrans` returns `M` unchanged, `MonadicBind` returns `Provider` unchanged, `ContainsValue::Value` is `T`, and `LiftValue` is the identity on `T` for both methods. - -`OkMonadic` and `ErrMonadic` implement the traits to branch on a `Result`, in mirror image of each other. For `ErrMonadic`, `ContainsValue>::Value` is `T` — the `Ok` payload is the value threaded forward — and `LiftValue>::lift_value` is `Ok`, so the continue branch is `Ok` and an `Err` short-circuits. For `OkMonadic` the roles are swapped: `ContainsValue>::Value` is `E`, and `lift_value` is `Err`, so the continue branch is `Err` and an `Ok` short-circuits. The `MonadicBind` impl of each base monad produces the corresponding `BindOk` or `BindErr` step over `IdentMonadic`, and the `MonadicTrans` impl wraps the marker in its transformer form (`OkMonadicTrans`, `ErrMonadicTrans`). - -The transformer forms `OkMonadicTrans` and `ErrMonadicTrans` implement the same traits by delegating one layer down to the base monad `M`. Their `ContainsValue` and `LiftValue` impls require `M: ContainsValue>`, peeling their own `Result` layer and handing the rest to `M`; their `MonadicTrans` impl composes transformers so a stack like `OkMonadicTrans` resolves layer by layer. This delegation is what allows monads to stack to arbitrary depth over nested result types. - -## Related constructs - -These traits are consumed by the monad providers in [monad providers](../providers/monad_providers.md): `PipeMonadic` uses `MonadicTrans` and `MonadicBind` to fold a handler list into a single pipeline provider, while `BindOk` and `BindErr` use `ContainsValue` and `LiftValue` in their `Computer` and `AsyncComputer` implementations to split and re-lift each step's output. The high-level picture of how the pieces fit — why a pipeline short-circuits and how the monads compose — is in [monadic handlers](../../concepts/monadic-handlers.md). The pipelines built from these traits implement the [`Computer`](../components/computer.md) family, so they slot into the same wiring as the [handler combinators](../providers/handler_combinators.md) `ComposeHandlers` and `PipeHandlers`, which compose handlers without the short-circuiting branch. - -## Source - -- The traits are defined in [crates/extra/cgp-monad/src/traits/](../../../crates/extra/cgp-monad/src/traits/) — `monadic_trans.rs`, `bind.rs`, `lift.rs`, and `value.rs`. -- Their implementations for each monad marker are in [crates/extra/cgp-monad/src/monadic/](../../../crates/extra/cgp-monad/src/monadic/) (`ident.rs`, `ok.rs`, `err.rs`). diff --git a/docs/reference/traits/optional_fields.md b/docs/reference/traits/optional_fields.md deleted file mode 100644 index ebf139a1..00000000 --- a/docs/reference/traits/optional_fields.md +++ /dev/null @@ -1,249 +0,0 @@ -# Optional and defaulted field extensions - -The optional-field extensions are the traits and providers that let a record be finalized even when some fields were never set, by filling the gaps from `Default` or treating them as optional, rather than requiring every field to be present. - -## Purpose - -These extensions solve the problem that the core builder family is deliberately strict: a partial record can be turned back into its concrete struct only once every field has been set, because [`FinalizeBuild`](has_builder.md) is implemented solely on the all-`IsPresent` configuration of the partial type. That strictness is exactly what catches a missing field at compile time, but it is too rigid for records where some fields have sensible defaults or are genuinely allowed to be absent. The traits in `cgp-field-extra` relax it in two controlled ways — filling unset fields with `Default::default()`, and tracking each field as an `Option` so absence is a runtime condition rather than a compile error — while reusing the same `UpdateField`-driven machinery underneath. - -The whole layer is built by composing three core mechanisms rather than inventing new ones. It reuses [`UpdateField`](has_builder.md) to move individual fields between states, [`TransformMapFields`](map_type.md) to re-wrap every field of a partial record at once, and the [`MapType`](map_type.md) markers `IsPresent`, `IsNothing`, and `IsOptional` to name what state each field is in. The extensions are therefore best understood as a small set of `TransformMap` natural transformations plus the entry-point traits that drive them, layered on top of the builder and extractor traits in core. - -## Definition - -The layer splits into two complementary capabilities — defaulted finalization and optional fields — that share the same underlying transform pattern. Defaulted finalization fills any unset field from `Default` so a record can be completed without setting everything; the optional-field traits convert a builder so every field becomes an `Option`, allow those optional slots to be set and replaced individually, and finalize either by requiring presence with an error or by defaulting. The `TransformMap` markers `TransformMapDefault` and `TransformOptional` carry the actual per-field conversions, and the entry-point traits `CanBuildWithDefault`, `CanFinalizeWithDefault`, `HasOptionalBuilder`, `ToOptional`, `SetOptional`, and `FinalizeOptional` expose them as usable operations. - -### `TransformMapDefault` — filling unset fields from `Default` - -`TransformMapDefault` is the [`TransformMap`](map_type.md) natural transformation that re-wraps every field into `IsPresent`, supplying `Default::default()` wherever a field has no value. It is a zero-sized marker with one impl per source marker, each describing how a field in that state becomes a present value: - -```rust -pub struct TransformMapDefault; - -impl TransformMap for TransformMapDefault { - fn transform_mapped(value: T) -> T { value } -} - -impl TransformMap for TransformMapDefault { - fn transform_mapped(_value: ()) -> T { T::default() } -} - -impl TransformMap for TransformMapDefault { - fn transform_mapped(value: Option) -> T { value.unwrap_or_default() } -} -``` - -A field that is already present passes through unchanged; a field that is `IsNothing` (never set) becomes its type's default; and a field that is `IsOptional` becomes the contained value or, when empty, the default. Because all three target `IsPresent`, applying this transform across a record leaves every field present, which is precisely the configuration `FinalizeBuild` accepts. - -### `CanFinalizeWithDefault` — finalize, defaulting whatever is unset - -`CanFinalizeWithDefault` finalizes a partial record into its target struct, defaulting any field that is not present. It is implemented for any partial value whose fields can be transformed by `TransformMapDefault` into the all-present configuration that `FinalizeBuild` then consumes: - -```rust -pub trait CanFinalizeWithDefault { - type Output; - fn finalize_with_default(self) -> Self::Output; -} - -impl CanFinalizeWithDefault for Builder -where - Builder: TransformMapFields, - Builder::Output: FinalizeBuild, -{ - type Output = Output; - fn finalize_with_default(self) -> Output { - self.transform_map_fields().finalize_build() - } -} -``` - -The body is the layer's core motion: `transform_map_fields` walks the record and applies `TransformMapDefault` to every field, producing an all-`IsPresent` partial value, and `finalize_build` turns that into the concrete struct. The strict presence check still applies — but it always succeeds, because the transform guarantees presence before `finalize_build` is reached. - -### `CanBuildWithDefault` — build from a source, defaulting the rest - -`CanBuildWithDefault` constructs the target struct by copying whatever fields a `Source` record shares with it and defaulting every remaining field. It chains the core [`CanBuildFrom`](has_builder.md) copy step into a defaulted finalize: - -```rust -pub trait CanBuildWithDefault { - fn build_with_default(source: Source) -> Self; -} - -impl CanBuildWithDefault for Target -where - Target: HasBuilder, - Builder: CanBuildFrom, - Builder::Output: CanFinalizeWithDefault, -{ - fn build_with_default(source: Source) -> Target { - Target::builder().build_from(source).finalize_with_default() - } -} -``` - -The pipeline reads top to bottom: start an empty builder for the target with [`HasBuilder`](has_builder.md), copy across every field the source and target have in common with `build_from`, then finalize with defaults for the fields the source did not supply. This is the field-level "widening cast" — turning a `Point2d` into a `Point3d` whose extra `z` is `0`, for instance — without naming any field explicitly. - -### `ToOptional` and `TransformOptional` — re-wrap every field as `Option` - -`ToOptional` converts a partial record so every field is wrapped in `IsOptional`, and `TransformOptional` is the `TransformMap` that performs the per-field conversion. Where `TransformMapDefault` targets `IsPresent`, `TransformOptional` targets `IsOptional`, mapping a present value to `Some` and an absent field to `None`: - -```rust -pub struct TransformOptional; - -impl TransformMap for TransformOptional { - fn transform_mapped(value: T) -> Option { Some(value) } -} - -impl TransformMap for TransformOptional { - fn transform_mapped(_value: ()) -> Option { None } -} - -pub trait ToOptional { - type Output; - fn to_optional(self) -> Self::Output; -} - -impl ToOptional for Context -where - Context: TransformMapFields, -{ - type Output = Context::Output; - fn to_optional(self) -> Self::Output { self.transform_map_fields() } -} -``` - -After `to_optional`, the partial type's every field marker is `IsOptional`, so each field's storage is an `Option`. A field already set becomes `Some`, an unset field becomes `None`, and from then on every field can be assigned or reassigned freely, because an `IsOptional` slot can always be overwritten. - -### `HasOptionalBuilder` — start an all-optional builder - -`HasOptionalBuilder` is the entry point that hands back a fresh builder in which every field is already optional. It composes `HasBuilder::builder()` with `ToOptional`, so the resulting builder starts with every field `None`: - -```rust -pub trait HasOptionalBuilder { - type Builder; - fn optional_builder() -> Self::Builder; -} - -impl HasOptionalBuilder for Context -where - Context: HasBuilder, - Context::Builder: ToOptional, -{ - type Builder = Builder; - fn optional_builder() -> Self::Builder { Self::builder().to_optional() } -} -``` - -This is the usual starting point for the optional-field workflow: `Context::optional_builder()` gives a builder whose fields can be set in any order and any number of times, deferring the decision about which fields must ultimately be present until finalization. - -### `SetOptional` — set or replace an optional field - -`SetOptional` sets the value of an optional field, optionally returning whatever value it replaced. It is implemented for any context whose `Tag` field is in the `IsOptional` state and stays there after the update: - -```rust -pub trait SetOptional { - type Value; - fn set(self, _tag: PhantomData, value: Self::Value) -> Self; - fn set_optional( - self, - _tag: PhantomData, - value: Self::Value, - ) -> (Option, Self); -} - -impl SetOptional for Context -where - Context: UpdateField, -{ - type Value = Context::Value; - fn set(self, tag, value) -> Self { self.set_optional(tag, value).1 } - fn set_optional(self, tag, value) -> (Option, Self) { - self.update_field(tag, Some(value)) - } -} -``` - -Both methods reduce to a single `UpdateField` call that writes `Some(value)` into the `IsOptional` slot. The crucial detail is that the field's marker is `IsOptional` both before and after — the `Mapper = IsOptional, Output = Context` bounds keep the builder's type unchanged — so a field can be set repeatedly, and `set_optional` returns the previous `Option` while `set` discards it. This is what makes the optional builder freely mutable, in contrast to the core `build_field` that consumes an absent slot exactly once. - -### `FinalizeOptional` — finalize, erroring on a genuinely missing field - -`FinalizeOptional` finalizes an optional builder into its concrete struct, succeeding only if every field actually holds a value and returning an error naming the first missing field otherwise. Unlike `CanFinalizeWithDefault`, it does not substitute defaults — absence is a recoverable runtime error rather than a silent fill: - -```rust -pub trait FinalizeOptional: PartialData { - fn finalize_optional(self) -> Result; -} -``` - -The implementation walks the target's [`HasFields`](has_fields.md) spine field by field. For each field it pulls the `Option` out of the `IsOptional` slot with `UpdateField`, and if the value is present it writes it back as `IsPresent` with `BuildField`; if the value is `None` it returns `Err(Tag::VALUE)` — the field's name as a static string — without finalizing. Only when every field has yielded a value does it call `finalize_build` on the now all-present partial value and return `Ok`. The error type `&'static str` is the missing field's own name, so a caller learns exactly which field was left unset. - -## Behavior - -Two finalization strategies sit on top of the same optional builder, differing only in how they treat a field that was never set. After `optional_builder` and a series of `set` calls, calling `finalize_with_default` completes the record by defaulting every unset field, whereas calling `finalize_optional` completes it only if nothing is missing and otherwise reports the missing field by name. The choice is made at the finalize call site, not when the builder is created, so the same builder value can be finalized either way depending on whether absence should be tolerated. - -The defaulted path and the optional path reuse the identical `transform_map_fields` recursion with different markers, which is why their behavior is so symmetric. `CanFinalizeWithDefault` drives `TransformMapDefault` toward `IsPresent`; `ToOptional` drives `TransformOptional` toward `IsOptional`. Both walk the same field spine, both rebuild the partial type one field at a time through `UpdateField`, and neither changes a value's runtime layout beyond wrapping or unwrapping an `Option` or substituting a default. The strict, all-present `FinalizeBuild` from core remains the only way a partial value becomes a concrete struct; these extensions simply guarantee the all-present configuration is reached before it is invoked. - -## Examples - -The optional-field workflow starts an all-optional builder, sets fields freely, and finalizes with one of the two strategies. Setting a field that already holds a value reports the replaced value, and finalizing with defaults fills whatever was never set: - -```rust -use cgp::prelude::*; -use cgp::extra::field::impls::{ - CanFinalizeWithDefault, FinalizeOptional, HasOptionalBuilder, SetOptional, -}; - -#[derive(CgpData)] -pub struct Context { - pub foo: String, - pub bar: u64, -} - -// Every field present: finalize_optional succeeds. -let builder = Context::optional_builder() - .set(PhantomData::, "foo".to_owned()) - .set(PhantomData::, 42); - -let (replaced, builder) = - builder.set_optional(PhantomData::, "bar".to_owned()); -assert_eq!(replaced, Some("foo".to_owned())); // the previous value comes back - -let context = builder.finalize_optional().unwrap(); -assert_eq!(context.foo, "bar"); -assert_eq!(context.bar, 42); - -// bar left unset: finalize_with_default fills it from Default. -let context = Context::optional_builder() - .set(PhantomData::, "foo".to_owned()) - .finalize_with_default(); -assert_eq!(context.foo, "foo"); -assert_eq!(context.bar, 0); -``` - -Had the second case used `finalize_optional` instead, it would have returned `Err("bar")` because `bar` was never set, rather than defaulting it to `0`. - -The defaulted-build path constructs a wider record from a narrower one in a single call, defaulting the fields the source lacks: - -```rust -use cgp::prelude::*; -use cgp::extra::field::impls::CanBuildWithDefault; - -#[derive(Debug, Clone, Eq, PartialEq, CgpData)] -struct Point2d { x: u64, y: u64 } - -#[derive(Debug, Clone, Eq, PartialEq, CgpData)] -struct Point3d { x: u64, y: u64, z: u64 } - -let point_2d = Point2d { x: 1, y: 2 }; -let point_3d = Point3d::build_with_default(point_2d); -assert_eq!(point_3d, Point3d { x: 1, y: 2, z: 0 }); // z defaulted -``` - -`build_with_default` copies `x` and `y` from the source through `build_from`, then defaults the unmatched `z` to `0` during the defaulted finalize. - -## Related constructs - -These extensions layer directly on the core builder family in [`HasBuilder`](has_builder.md): `CanBuildWithDefault` chains its `CanBuildFrom` copy step and `HasBuilder` entry point, and every finalize path ultimately calls `FinalizeBuild`. They are driven by the [`MapType`](map_type.md) machinery — `TransformMapDefault` and `TransformOptional` are `TransformMap` natural transformations, and `TransformMapFields` is the recursion that applies them across a whole record, re-marking each field's `IsPresent`/`IsNothing`/`IsOptional` state. `SetOptional` and `FinalizeOptional` reduce to the same `UpdateField` primitive used by the core `BuildField` and `TakeField`. The partial types these operate on are generated by [`#[derive(BuildField)]`](../derives/derive_build_field.md) (also exposed through `#[derive(CgpData)]`). The enum-side counterpart that takes fields apart variant by variant is [`ExtractField`](extract_field.md). - -## Source - -- The extensions are defined in `cgp-field-extra`: `CanBuildWithDefault`, `CanFinalizeWithDefault`, and `TransformMapDefault` in [crates/extra/cgp-field-extra/src/impls/build_default.rs](../../../crates/extra/cgp-field-extra/src/impls/build_default.rs); `FinalizeOptional` in [crates/extra/cgp-field-extra/src/impls/finalize_optional.rs](../../../crates/extra/cgp-field-extra/src/impls/finalize_optional.rs); `SetOptional` in [crates/extra/cgp-field-extra/src/impls/set_optional.rs](../../../crates/extra/cgp-field-extra/src/impls/set_optional.rs); and `HasOptionalBuilder`, `ToOptional`, and `TransformOptional` in [crates/extra/cgp-field-extra/src/impls/to_optional.rs](../../../crates/extra/cgp-field-extra/src/impls/to_optional.rs). -- They build on the core traits in [crates/core/cgp-field/src/traits/](../../../crates/core/cgp-field/src/traits/) (`UpdateField`, `BuildField`, `FinalizeBuild`, `PartialData`, `HasFields`, `TransformMap`, `TransformMapFields`) and the `MapType` markers in [crates/core/cgp-field/src/impls/map_type.rs](../../../crates/core/cgp-field/src/impls/map_type.rs). diff --git a/docs/reference/traits/product_ops.md b/docs/reference/traits/product_ops.md deleted file mode 100644 index bda0efb5..00000000 --- a/docs/reference/traits/product_ops.md +++ /dev/null @@ -1,113 +0,0 @@ -# Product operations: `AppendProduct`, `ConcatProduct`, `MapFields` - -`AppendProduct`, `ConcatProduct`, and `MapFields` are type-level operations that transform [`Cons`](../types/cons.md)/`Nil` products (and, for `MapFields`, [`Either`](../types/either.md)/`Void` sums), letting generic code grow and reshape a context's field list without naming the concrete types. - -## Purpose - -These three traits are the list algebra that CGP's structural machinery is built from. A struct's whole shape is a type-level product of fields — a [`Cons`](../types/cons.md) spine terminated by `Nil`, produced by [`#[derive(HasFields)]`](../derives/derive_has_fields.md) — and an enum's shape is the analogous [`Either`](../types/either.md) sum terminated by `Void`. To process such a shape generically, code needs to compute new shapes from old ones at the type level: add one field to the end of a product, splice two products together, or rewrite every entry uniformly. `AppendProduct`, `ConcatProduct`, and `MapFields` are exactly those three computations, each expressed as a trait whose recursion over the spine the compiler evaluates during type checking. - -Because they operate purely on types, these operations carry no runtime cost and impose no ordering on the program — they are pure functions from type lists to type lists. They are the plumbing beneath higher-level constructs: building a record one field at a time appends to a product, merging two records concatenates them, and producing a partial-record representation maps a marker over every field. - -## Definition - -`AppendProduct` adds a single entry to the end of a product, exposing the extended product as its `Output`. It recurses down the [`Cons`](../types/cons.md) spine, rebuilding each node, until it reaches `Nil`, where it inserts the new `Cons`: - -```rust -pub trait AppendProduct { - type Output; -} - -impl AppendProduct for Nil { - type Output = Cons; -} - -impl AppendProduct for Cons -where - Tail: AppendProduct, -{ - type Output = Cons; -} -``` - -`ConcatProduct` splices a second product onto the end of the first. It has the same spine recursion, but at `Nil` it substitutes the entire `Items` list rather than a single element, so the result is the first product's entries followed by all of the second's: - -```rust -pub trait ConcatProduct { - type Output; -} - -impl ConcatProduct for Nil { - type Output = Items; -} - -impl ConcatProduct for Cons -where - Tail: ConcatProduct, -{ - type Output = Cons; -} -``` - -`MapFields` rewrites every entry of a list through a [`MapType`](map_type.md) marker, exposing the rewritten list as `Mapped`. Unlike the other two, it is defined over both spines: for the product spine it walks `Cons`/`Nil`, and for the sum spine it walks `Either`/`Void`, in each case applying `Mapper::Map` to the head and recursing on the tail: - -```rust -pub trait MapFields { - type Mapped; -} - -impl MapFields for Cons -where - Mapper: MapType, - Rest: MapFields, -{ - type Mapped = Cons, Rest::Mapped>; -} - -impl MapFields for Nil { - type Mapped = Nil; -} -``` - -The `Either`/`Void` impls mirror these exactly, replacing `Cons` with `Either` and `Nil` with `Void`, so the same `Mapper` applies uniformly whether the shape is a record or a tagged union. - -## Behavior - -The defining behavior of all three is that they are evaluated at compile time by the trait resolver walking the spine. `AppendProduct` and `ConcatProduct` preserve every existing entry and differ only in what they graft on at the `Nil` terminator — one element versus a whole list — which makes append a special case of concat with a single-element tail. Neither touches the values' types beyond reordering them into a longer list. - -`MapFields` is the transforming operation: it leaves the spine's length and shape unchanged but replaces each entry type `T` with `Mapper::Map`. With `IsPresent` it is the identity; with `IsNothing` it collapses every entry to the unit type; with `IsOptional` it wraps every entry in `Option`. This is how a concrete product of field values becomes the field list of a partial-record representation, where each field is independently marked. Because `MapFields` covers both spines, the same marker turns a struct's product into a partial struct and an enum's sum into a partial enum. - -## Examples - -These operations underlie the extensible-record machinery, so they are most visible through its higher-level interface, but they can be exercised directly on type-level lists. Appending and concatenating compute new product types: - -```rust -use cgp::prelude::*; - -type Base = Product![Field]; - -// AppendProduct adds one field to the end -type WithPort = >>::Output; -// = Product![Field, Field] - -// ConcatProduct splices a whole product onto the end -type Extra = Product![Field]; -type Full = >::Output; -// = Product![host, port, tls] -``` - -`MapFields` rewrites every entry uniformly. Applying `IsOptional` turns a product of plain values into a product of optionals, the shape a partial builder uses to track which fields are not yet filled: - -```rust -type Fields = Product![String, u16, bool]; -type Optional = >::Mapped; -// = Product![Option, Option, Option] -``` - -## Related constructs - -These operations act on the [`Cons`](../types/cons.md)/`Nil` product spine and, for `MapFields`, the [`Either`](../types/either.md)/`Void` sum spine — the two type-level lists that [`#[derive(HasFields)]`](../derives/derive_has_fields.md) produces from structs and enums. The convenient surface syntax for those lists is the [`Product!`](../macros/product.md) macro. `MapFields` applies a [`MapType`](map_type.md) marker to every entry, which is the same per-field state vocabulary that the builder family in [`HasBuilder`](has_builder.md) and the extractor family in [`ExtractField`](extract_field.md) use to track presence; `AppendProduct` and `ConcatProduct` are the list-growing operations behind assembling and merging records. - -## Source - -- `AppendProduct` is defined in [crates/core/cgp-field/src/traits/append_product.rs](../../../crates/core/cgp-field/src/traits/append_product.rs), `ConcatProduct` in [crates/core/cgp-field/src/traits/concat_product.rs](../../../crates/core/cgp-field/src/traits/concat_product.rs), and `MapFields` in [crates/core/cgp-field/src/traits/map_fields.rs](../../../crates/core/cgp-field/src/traits/map_fields.rs). -- The [`MapType`](map_type.md) markers `MapFields` applies are in [crates/core/cgp-field/src/impls/map_type.rs](../../../crates/core/cgp-field/src/impls/map_type.rs), and the [`Cons`](../types/cons.md)/`Nil`/[`Either`](../types/either.md)/`Void` building blocks are under [crates/core/cgp-field/src/types/](../../../crates/core/cgp-field/src/types/). diff --git a/docs/reference/traits/static_format.md b/docs/reference/traits/static_format.md deleted file mode 100644 index 4d8d21cf..00000000 --- a/docs/reference/traits/static_format.md +++ /dev/null @@ -1,97 +0,0 @@ -# `StaticFormat`, `StaticString`, `ConcatPath` - -`StaticFormat`, `StaticString`, and `ConcatPath` turn CGP's type-level strings and paths back into runtime data — formatting a [`Chars`](../types/chars.md) chain character by character, decoding a [`Symbol!`](../macros/symbol.md) into a `&'static str` constant, and concatenating two type-level paths into one. - -## Purpose - -These traits close the loop between CGP's compile-time names and the runtime world. CGP encodes field and variant names as types — a [`Symbol!`](../macros/symbol.md) is a length plus a [`Chars`](../types/chars.md) list, one node per character — so that names can drive trait resolution. But a program eventually needs those names as ordinary strings: to print a field name in an error message, to build a key, or to render a navigation path. `StaticFormat` and `StaticString` are the two ways to recover the string, and `ConcatPath` is the corresponding type-level operation on paths. - -`StaticFormat` recovers the string lazily, by writing into a formatter; it is what backs the `Display` impl on `Symbol` and `Chars`, so a type-level string can be printed with `to_string()` or `{}`. `StaticString` recovers it eagerly, as a compile-time `&'static str` constant computed by const evaluation, so the decoded string is available wherever a `const` is needed and costs nothing at runtime. `ConcatPath` works one level up: a [`PathCons`](../types/path_cons.md) path is a type-level list of path segments, and joining two paths — the common operation when composing nested accessors — is splicing one such list onto another. - -## Definition - -`StaticFormat` is a trait with a single associated function that writes the type-level string into a `Formatter`. The implementor is the type-level string itself, so the function takes no `self` — there is no runtime value, only the type: - -```rust -pub trait StaticFormat { - fn fmt(f: &mut Formatter<'_>) -> Result<(), fmt::Error>; -} -``` - -It is implemented by recursion over the [`Chars`](../types/chars.md) spine. Each `Chars` writes its own `CHAR` and then defers to `Tail`, and the terminating `Nil` writes nothing: - -```rust -impl StaticFormat for Chars -where - Tail: StaticFormat, -{ - fn fmt(f: &mut Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{CHAR}")?; - Tail::fmt(f) - } -} - -impl StaticFormat for Nil { /* writes nothing */ } -``` - -`StaticString` exposes the decoded string as an associated constant rather than a formatting action: - -```rust -pub trait StaticString { - const VALUE: &'static str; -} -``` - -It is implemented for every type via a blanket impl over an internal `StaticBytes` trait. `Symbol` computes a `[u8; LEN]` byte array at const-evaluation time by walking the `Chars` list and UTF-8-encoding each character into the array, then `StaticString::VALUE` validates those bytes as UTF-8 and exposes the result as a `&'static str`. The `LEN` const parameter of the `Symbol` is the precomputed byte length that sizes this array, which is why `Symbol!` records a byte length rather than a character count. - -`ConcatPath` joins two type-level paths, exposing the joined path as `Output`. Both the input and output may be unsized, since path types are markers: - -```rust -pub trait ConcatPath { - type Output: ?Sized; -} -``` - -It recurses over the [`PathCons`](../types/path_cons.md) spine just as `ConcatProduct` does over a product: each `PathCons` keeps its `Head` and concatenates `Other` onto the `Tail`, and the terminating `Nil` becomes `Other` itself, so the result is the first path's segments followed by the second's. - -## Behavior - -`StaticFormat` and `StaticString` decode the same type-level string but differ in when and how. `StaticFormat` runs at the moment of formatting, recursing through the `Chars` nodes and emitting each character into the formatter; because both `Symbol` and `Chars` implement `Display` by delegating to it, any type-level string can be turned into an owned `String` or interpolated directly. `StaticString` instead produces the string once, in a `const` context, so `::VALUE` is a plain `&'static str` with no per-call work — preferable when the name is needed as a constant or in a hot path. Both faithfully round-trip multi-byte Unicode: the byte length in `Symbol` accounts for UTF-8 width, and the empty symbol decodes to `""`. - -`ConcatPath` is a pure type-level computation evaluated during trait resolution. It never touches values; it only names the combined path type, which a getter or accessor then uses to descend through nested fields. - -## Examples - -A type-level string can be recovered both ways. The `Display` route reconstructs it at runtime, and the `StaticString` route exposes it as a constant: - -```rust -use cgp::prelude::*; -use cgp::core::field::traits::StaticString; - -// via StaticFormat / Display -let s = ::default(); -assert_eq!(s.to_string(), "hello"); - -// via StaticString — a compile-time constant, multi-byte safe -assert_eq!(::VALUE, "世界你好"); -assert_eq!(::VALUE, ""); -``` - -`ConcatPath` composes two paths into one at the type level, the operation behind chaining nested accessors: - -```rust -type Outer = Path!(a.b); -type Inner = Path!(c.d); -type Joined = >::Output; -// the path a.b.c.d -``` - -## Related constructs - -`StaticFormat` and `StaticString` decode the [`Chars`](../types/chars.md) chain and [`Symbol`](../macros/symbol.md) wrapper that the [`Symbol!`](../macros/symbol.md) macro builds from a string literal — the type-level string at the heart of CGP's field naming. `ConcatPath` operates on the [`PathCons`](../types/path_cons.md) spine constructed by the [`Path!`](../macros/path.md) macro, and is the path-level analogue of the product-level `ConcatProduct`. Together they let the names that drive [`HasField`](has_field.md) lookups and nested-getter composition surface as ordinary strings and paths. - -## Source - -- `StaticFormat` and its `Chars`/`Nil` impls are defined in [crates/core/cgp-base-types/src/traits/static_format.rs](../../../crates/core/cgp-base-types/src/traits/static_format.rs); the `Display` impls that delegate to it are on the [`Chars`](../types/chars.md) and [`Symbol`](../macros/symbol.md) types in [crates/core/cgp-base-types/src/types/](../../../crates/core/cgp-base-types/src/types/). -- `StaticString`, with its const-evaluated UTF-8 decoding, is in [crates/core/cgp-field/src/traits/static_string.rs](../../../crates/core/cgp-field/src/traits/static_string.rs). -- `ConcatPath` is in [crates/core/cgp-base-types/src/traits/concat_path.rs](../../../crates/core/cgp-base-types/src/traits/concat_path.rs), and `PathCons` in [crates/core/cgp-base-types/src/types/path.rs](../../../crates/core/cgp-base-types/src/types/path.rs). diff --git a/docs/reference/types/chars.md b/docs/reference/types/chars.md deleted file mode 100644 index 1e15be52..00000000 --- a/docs/reference/types/chars.md +++ /dev/null @@ -1,77 +0,0 @@ -# `Chars` and `Symbol` - -`Chars` is a type-level character list and `Symbol` wraps such a list together with the string's byte length — together they are CGP's encoding of a string as a type. - -## Purpose - -`Chars` and `Symbol` exist so that a string can be a *type* rather than a value, which is what lets a field name participate in trait resolution. CGP keys field access by a `Tag` type: to read a field called `name`, something must stand in for the string `"name"` at the type level so the compiler can match one `HasField` impl against another purely from the tag. A `Symbol` is that something — a unique type whose entire identity is the character sequence it encodes, so two symbols built from the same string are the same type and two built from different strings are different types. - -The reason the encoding is a *list of characters* rather than a single const value is a limitation in stable Rust: a `String` or `&str` cannot be used as a const-generic parameter, but a single `char` can. CGP works around this by spelling the string out one character at a time through a recursive `Chars` spine, the same way a heterogeneous list spells out its elements through a [`Cons`](cons.md)/[`Nil`](cons.md) spine. `Chars` is the specialized analogue of `Cons` in which the head is a `const char` rather than a type. - -These two types are almost never written by hand. They are produced by the [`Symbol!`](../macros/symbol.md) macro, which takes a string literal and folds it into the corresponding `Symbol`/`Chars`/`Nil` chain. This document describes the runtime types and the traits they carry; the construction syntax and the macro's right-fold expansion live in the `Symbol!` macro document. - -## Definition - -`Chars` is a zero-sized struct carrying one character as a const parameter and the rest of the string as its `Tail`: - -```rust -pub struct Chars(pub PhantomData); -``` - -The `Tail` is expected to be either the next `Chars` node or `Nil` to mark the end of the string, so a chain of `Chars` terminated by `Nil` is a type-level list of characters. `Chars` carries no runtime data — the character lives in the const parameter and the tail in a `PhantomData` — so the whole list is erased to a zero-sized value at runtime. - -`Symbol` wraps a `Chars` chain and records the string's byte length as a separate const parameter: - -```rust -pub struct Symbol(pub PhantomData); -``` - -The `Chars` type parameter is the head of the character list (despite the name, it is the whole chain, not a single node), and `LEN` is the string's byte length — the value of `str::len()`. The length is stored explicitly because stable Rust cannot compute the length of a `Chars` chain inside a const-generic context; baking it into the type lets length-dependent code read the size off the type directly instead of recursing through the list. Because `LEN` is the *byte* length, a four-character string of multi-byte scalars such as `Symbol!("世界你好")` records `12`, while its character list has one `Chars` node per Unicode scalar value. - -## Behavior - -Both types reconstruct their original string on demand through the [`StaticFormat`](../traits/static_format.md) trait, which formats a type-level string into a `Formatter` without needing a value. `Chars` implements `StaticFormat` by writing `CHAR` and then recursing into `Tail::fmt`, and `Nil` terminates the recursion by writing nothing; `Symbol` forwards to its inner `Chars`. Each type also has a `Display` impl that defers to `StaticFormat`, so `::default().to_string()` yields `"hello"`. - -The other trait the `LEN` parameter enables is [`StaticString`](../traits/static_format.md), which exposes the string as a single `const VALUE: &'static str` rather than a formatting routine. Its blanket impl decodes the `Symbol`'s characters into a `[u8; LEN]` at compile time — this is the consumer for which `LEN` exists, since the byte buffer must be sized by a const. A reader needing the string as a value at runtime uses `Display`; a reader needing it as a const uses `StaticString::VALUE`. - -`Symbol` also implements `Default` unconditionally (it is a zero-sized marker, so the default is just the empty `PhantomData`), which is what allows a `Symbol!("…")` type to be materialized as a value where one is needed, such as the tag passed to `get_field`. - -## Examples - -A type-level string most often appears as the `Tag` in a [`HasField`](../traits/has_field.md) bound, where it names the field a provider reads: - -```rust -use cgp::prelude::*; - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasField, -{ - fn greet(&self) { - println!("Hello, {}!", self.get_field(PhantomData::)); - } -} -``` - -The same type can be constructed directly and inspected at runtime through its `Display` impl, which walks the `Chars` chain to rebuild the string: - -```rust -use cgp::prelude::*; - -let s = ::default(); -assert_eq!(s.to_string(), "hello"); -``` - -Because the encoding is a list, an empty string is `Symbol<0, Nil>` — a `Symbol` whose character list is just the terminator and whose recorded length is zero. - -## Related constructs - -`Chars` is the character-level specialization of the [`Cons`](cons.md)/`Nil` product spine: where `Cons` holds an arbitrary type as its head, `Chars` holds a `const char`, and both terminate in `Nil`. The pair is built by the [`Symbol!`](../macros/symbol.md) macro and consumed by [`StaticFormat`/`StaticString`](../traits/static_format.md) for display and const access. A `Symbol` is the field-name half of CGP's tagging scheme — its position counterpart for tuple fields is [`Index`](index.md) — and the tags it produces are matched by [`HasField`](../traits/has_field.md) and carried inside the [`Field`](field.md) entries of a struct's [`HasFields`](../traits/has_fields.md) representation. Symbols also appear as the lowercase segments of a type-level [`PathCons`](path_cons.md) path built by [`Path!`](../macros/path.md). - -## Source - -- The runtime types are defined in [crates/core/cgp-base-types/src/types/chars.rs](../../../crates/core/cgp-base-types/src/types/chars.rs) (`Chars`) and [crates/core/cgp-base-types/src/types/symbol.rs](../../../crates/core/cgp-base-types/src/types/symbol.rs) (`Symbol`), with `Nil` in [crates/core/cgp-base-types/src/types/nil.rs](../../../crates/core/cgp-base-types/src/types/nil.rs). -- The `StaticFormat` impls that drive `Display` are in [crates/core/cgp-base-types/src/traits/static_format.rs](../../../crates/core/cgp-base-types/src/traits/static_format.rs), and the const-decoding `StaticString` impl that consumes `LEN` is in [crates/core/cgp-field/src/traits/static_string.rs](../../../crates/core/cgp-field/src/traits/static_string.rs). -- The constructing macro is [`Symbol!`](../macros/symbol.md). -- For how it is generated and the index of tests, see the implementation document [implementation/entrypoints/symbol.md](../../implementation/entrypoints/symbol.md). diff --git a/docs/reference/types/cons.md b/docs/reference/types/cons.md deleted file mode 100644 index 8c228daf..00000000 --- a/docs/reference/types/cons.md +++ /dev/null @@ -1,75 +0,0 @@ -# `Cons` and `Nil` - -`Cons` and `Nil` are the two cells of CGP's product spine — a recursive, right-nested type-level list that generic code folds over to handle any struct's fields one at a time. - -## Purpose - -`Cons` and `Nil` exist to represent an ordered sequence of types as a single type, so that a collection of fields can be reasoned about generically. A plain Rust tuple holds several things at once but cannot be taken apart by generic code element by element; a recursive list can. By pairing a head with the rest of the list and terminating with an empty marker, CGP builds an *anonymous product type* — a record-shaped value whose structure a provider can walk without knowing the concrete struct it came from. - -The product spine is what makes structural, field-by-field operations possible across every struct uniformly. Because a struct's fields are exposed as one list type through [`HasFields`](../traits/has_fields.md), a provider written once to recurse over `Cons`/`Nil` can iterate, transform, read, or rebuild *any* struct's fields. Each step handles the `Head`, then recurses into the `Tail`, until it reaches `Nil` and stops. This recursion over a fixed two-case shape — a pair or the empty list — is the mechanism behind builders, extractors, and field mappers alike. - -`Cons` and `Nil` are the constructible building blocks; the [`Product!`](../macros/product.md) macro is how a programmer writes a list of them. `Product![A, B, C]` is sugar for the right-nested `Cons` chain, and the value macro `product![a, b, c]` builds a matching value. The list elements are most often [`Field`](field.md) entries pairing a name with a value, so a struct's layout becomes a `Product!` of `Field` cells over this same spine. - -## Definition - -`Cons` is a tuple struct holding the first element and the rest of the list, and `Nil` is a unit struct marking the end: - -```rust -#[derive(Eq, PartialEq, Clone, Default, Debug)] -pub struct Cons(pub Head, pub Tail); - -#[derive(Eq, PartialEq, Clone, Default, Debug)] -pub struct Nil; -``` - -The `Head` parameter is the first element's type and the `Tail` parameter is the rest of the list — itself another `Cons` or, at the end, `Nil`. Both positional fields are public, so `Cons(head, tail)` constructs a cell and `.0`/`.1` reach its parts. `Nil` carries no data; used as a `Tail` it terminates the chain, and used on its own it is the empty list. Both derive `Eq`, `PartialEq`, `Clone`, `Default`, and `Debug`, so a list of values that themselves implement these traits inherits them structurally — equality compares head-to-head down the chain, and `Default` yields the all-defaults list. - -## Behavior - -A list of any length is a `Cons` chain ending in `Nil`, nested to the right. The type `Product![A, B, C]` is `Cons>>`, and the empty `Product![]` is just `Nil`. The corresponding value is built with the tuple-struct constructor, `Cons(a, Cons(b, Cons(c, Nil)))`, so a `product!` value is an ordinary owned value whose type is exactly what `Product!` produces over the same elements' types. Because `Cons` and `Nil` are real structs, nothing about the list is virtual or boxed; the whole structure is flat data laid out by nesting. - -Generic code consumes the spine by recursing on its two cases. A trait implemented for `Nil` supplies the base case — the empty list — and a blanket impl for `Cons` supplies the recursive step, typically constraining `Tail` to implement the same trait so the recursion bottoms out at `Nil`. This pairing of a `Nil` impl with a `Cons` impl is the standard shape for any operation that folds over a product, and it is how the field machinery processes a struct of arbitrary width without per-field code. - -## Examples - -The product spine appears most visibly as the `Fields` of a struct that derives [`HasFields`](../derives/derive_has_fields.md), where the [`Product!`](../macros/product.md) sugar hides the `Cons`/`Nil` chain: - -```rust -use cgp::prelude::*; - -#[derive(HasFields)] -pub struct Person { - pub name: String, - pub age: u8, -} - -// generated: -// impl HasFields for Person { -// type Fields = Product![ -// Field, -// Field, -// ]; -// // i.e. Cons, -// // Cons, Nil>> -// } -``` - -A standalone list type and a matching value can also be written directly, which expands to the nested `Cons` form: - -```rust -use cgp::prelude::*; - -type Row = Product![u32, String, bool]; -let row: Row = product![1, "hi".to_string(), true]; -// Row == Cons>> -// row == Cons(1, Cons("hi".to_string(), Cons(true, Nil))) -``` - -## Related constructs - -`Cons`/`Nil` are the product (record-like) spine; their sum (choice-like) counterpart is [`Either`/`Void`](./either.md), which shares the same right-nested shape but branches at each step and terminates in the uninhabited `Void` rather than the constructible `Nil`. A list of these cells is written with the [`Product!`](../macros/product.md) macro, and its elements are usually [`Field`](field.md) entries whose tags come from [`Symbol!`](../macros/symbol.md) or [`Index`](index.md). The whole list is what [`#[derive(HasFields)]`](../derives/derive_has_fields.md) assigns to a struct and what [`HasFields`](../traits/has_fields.md) exposes. The `Chars` list inside [`Symbol!`](../macros/symbol.md) is a specialized version of this same spine, with a `const char` head in place of a type. - -## Source - -- `Cons` is defined in [crates/core/cgp-base-types/src/types/cons.rs](../../../crates/core/cgp-base-types/src/types/cons.rs) and `Nil` in [crates/core/cgp-base-types/src/types/nil.rs](../../../crates/core/cgp-base-types/src/types/nil.rs). -- The `Product!`/`product!` macros that fold elements onto this spine are driven by the constructs under [crates/macros/cgp-macro-core/src/types/product/](../../../crates/macros/cgp-macro-core/src/types/product/), and the `HasFields` machinery that recurses over it lives in [crates/core/cgp-field/src/traits/has_fields.rs](../../../crates/core/cgp-field/src/traits/has_fields.rs). diff --git a/docs/reference/types/either.md b/docs/reference/types/either.md deleted file mode 100644 index 38236734..00000000 --- a/docs/reference/types/either.md +++ /dev/null @@ -1,84 +0,0 @@ -# `Either` and `Void` - -`Either` and `Void` are the two cells of CGP's sum spine — a recursive, right-nested type-level choice that generic code walks to handle any enum's variants one branch at a time. - -## Purpose - -`Either` and `Void` exist to represent a choice among several types as a single type, so that the variants of an enum can be reasoned about generically. Where the product spine ([`Cons`/`Nil`](./cons.md)) holds a value for *every* element at once, the sum spine holds a value for exactly *one* of its branches — a tagged union, or *anonymous sum type*. By branching at each step and terminating with an uninhabited marker, CGP builds a coproduct whose structure a provider can walk without knowing the concrete enum it came from. - -The sum spine is what makes structural, variant-by-variant operations possible across every enum uniformly. Because an enum's variants are exposed as one sum type through [`HasFields`](../traits/has_fields.md), a provider written once to recurse over the `Either`/`Void` branches can match, dispatch on, or construct *any* enum's variants. This is the basis for CGP's extensible-variant machinery: each variant is reached by walking the nested branches rather than by writing a hand-rolled `match` against a fixed enum. - -`Either` and `Void` are the building blocks; the [`Sum!`](../macros/sum.md) macro is how a programmer writes a chain of them. `Sum![A, B, C]` is sugar for the right-nested `Either` chain terminated by `Void`. The branches are most often [`Field`](field.md) entries pairing a variant name with its payload, so an enum's shape becomes a `Sum!` of `Field` branches over this spine. - -## Definition - -`Either` is a two-case enum selecting the head or deferring to the rest of the chain, and `Void` is an empty enum that can never be constructed: - -```rust -#[derive(Eq, PartialEq, Debug, Clone)] -pub enum Either { - Left(Head), - Right(Tail), -} - -#[derive(Eq, PartialEq, Debug, Clone)] -pub enum Void {} -``` - -The `Head` parameter is the type of the current branch and `Tail` is the rest of the chain — itself another `Either` or, at the end, `Void`. The `Left(Head)` case carries a value of the head type; the `Right(Tail)` case carries a value belonging somewhere further down the chain. `Void` has no variants, so it has no values: it is uninhabited. Both types derive `Eq`, `PartialEq`, `Debug`, and `Clone`, so a sum of values that implement these traits inherits them. - -## Behavior - -A sum of any width is an `Either` chain ending in `Void`, nested to the right. The type `Sum![A, B, C]` is `Either>>`, and the empty `Sum![]` is just `Void`. A value selects exactly one branch by how deep it sits: `Left(a)` is an `A`, `Right(Left(b))` is a `B`, and `Right(Right(Left(c)))` is a `C`. Reaching the `Void` position would mean the value matched none of the listed branches — which is impossible, because `Void` has no values, so the chain is closed off at its end. - -Generic code consumes the sum by recursing on its two cases, mirroring how it folds the product spine but branching instead of pairing. A `Left` is handled directly as the head; a `Right` defers to a trait impl on the `Tail`, recursing until a `Left` is found. The base case is the `Void` terminator, and here the difference from the product spine matters: a product ends in [`Nil`](cons.md), a constructible empty record, but a sum ends in the *uninhabited* `Void`, because an empty choice has no value to pick. `Void` is functionally the never type, used specifically to mark the end of a sum. - -The uninhabitedness of `Void` is load-bearing for the extractor machinery, where [`FinalizeExtract`](../traits/extract_field.md) relies on it to discharge the impossible remainder. After an extractor has tried every variant of a sum and matched none, the leftover value has type `Void` — a value that cannot exist. `FinalizeExtract for Void` turns that into any required type with an empty `match self {}`, since there are no cases to handle, so a fully-handled sum extraction is total at compile time with no unreachable runtime branch. A constructible terminator like `Nil` could not be discharged this way; only an uninhabited type lets the compiler accept the empty match. - -## Examples - -The sum spine appears most visibly as the `Fields` of an enum that derives [`HasFields`](../derives/derive_has_fields.md), where the [`Sum!`](../macros/sum.md) sugar hides the `Either`/`Void` chain: - -```rust -use cgp::prelude::*; - -#[derive(HasFields)] -pub enum Shape { - Circle(f64), - Rectangle { width: f64, height: f64 }, -} - -// generated (schematically): -// impl HasFields for Shape { -// type Fields = Sum![ -// Field, -// Field, -// Field, -// ]>, -// ]; -// // i.e. Either, -// // Either, Void>> -// } -``` - -A standalone sum type can also be written directly, and a value picks one branch by its nesting depth: - -```rust -use cgp::prelude::*; - -type Token = Sum![u32, String, bool]; -// Token == Either>> - -let t: Token = Either::Right(Either::Left("hi".to_string())); // the String branch -``` - -## Related constructs - -`Either`/`Void` are the sum (choice-like) spine; their product (record-like) counterpart is [`Cons`/`Nil`](./cons.md), which shares the same right-nested shape but pairs a head with the rest at each step and terminates in the constructible `Nil` rather than the uninhabited `Void`. A chain of these cells is written with the [`Sum!`](../macros/sum.md) macro, and its branches are usually [`Field`](field.md) entries whose tags come from [`Symbol!`](../macros/symbol.md). The whole chain is what [`#[derive(HasFields)]`](../derives/derive_has_fields.md) assigns to an enum and what [`HasFields`](../traits/has_fields.md) exposes, and `Void`'s uninhabitedness is what the `FinalizeExtract` part of the [extractor family](../traits/extract_field.md) depends on to close a total variant match. - -## Source - -- `Either` and `Void` are both defined in [crates/core/cgp-field/src/types/sum.rs](../../../crates/core/cgp-field/src/types/sum.rs). -- The `Sum!` macro that folds element types onto this spine is the `SumType` construct in [crates/macros/cgp-macro-core/src/types/sum.rs](../../../crates/macros/cgp-macro-core/src/types/sum.rs). -- `FinalizeExtract for Void`, which discharges the uninhabited remainder of a variant extraction, lives in [crates/core/cgp-field/src/traits/extract_field.rs](../../../crates/core/cgp-field/src/traits/extract_field.rs), and the enum `HasFields` derive that emits a `Sum!` of `Field` branches is under [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/sum.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/sum.rs). diff --git a/docs/reference/types/field.md b/docs/reference/types/field.md deleted file mode 100644 index b79e8c6b..00000000 --- a/docs/reference/types/field.md +++ /dev/null @@ -1,75 +0,0 @@ -# `Field` - -`Field` is the named-entry building block of CGP's structural data, pairing a `Value` with a type-level `Tag` that records the field's name without carrying any runtime cost. - -## Purpose - -`Field` exists so that a field's *name* and its *value* travel together as a single type, letting generic code know not just what a struct holds but what each piece is called. A bare [`Product!`](../macros/product.md) list such as `Product![String, u8]` records only the types and their order; it cannot tell `name: String` from any other `String`. Wrapping each element in a `Field` — `Field` — attaches the name as a phantom type, so the structural representation of a struct is self-describing: a provider walking the list can match on the tag to find the field it wants. - -The tag is carried as a phantom type precisely because the name is needed only at compile time, for trait resolution and dispatch, never at run time. A `Field` is therefore exactly as large as its `Value` — the `PhantomData` occupies no space — so encoding names this way costs nothing at run time while making field-by-field generic code possible. This is the same string-as-type trick that [`Symbol!`](../macros/symbol.md) provides for names and [`Index`](index.md) provides for tuple positions; `Field` is where that tag meets the value it labels. - -`Field` is the element type that fills both spines of structural data. In a product (a record), the [`HasFields`](../traits/has_fields.md) representation of a struct is a `Product!` of `Field` entries, one per field. In a sum (an enum), it is a [`Sum!`](../macros/sum.md) of `Field` entries, one per variant, where the `Value` is the variant's payload. The same `Field` shape names a field in a record and a variant in an enum. - -## Definition - -`Field` is a two-parameter struct holding the value alongside a phantom tag: - -```rust -pub struct Field { - pub value: Value, - pub phantom: PhantomData, -} -``` - -The `Tag` parameter is the type-level name of the field. It is a phantom — it appears only inside `PhantomData` and never in a runtime field — and is typically a type-level string such as `Symbol!("name")` for a named field or a type-level number such as `Index<0>` for a tuple-struct position. The `Value` parameter is the field's actual type, and `value` is the only data the struct stores. Aside from the tag, a `Field` is a thin wrapper around its `Value`. - -## Behavior - -A `Field` is built from a value with no tag argument, because the tag is fixed by the target type rather than passed in. The `From` impl constructs the wrapper directly, so `let f: Field = "Alice".to_string().into();` fills in `value` and sets `phantom` to `PhantomData`. The tag is inferred from the expected type, which is why generated `HasFields` code can build each entry with a plain `.into()`. - -The remaining trait impls all defer to the value and ignore the tag, so a `Field` behaves like its `Value` for comparison and printing. `Debug` forwards to the value's `Debug` (the tag is not shown), and `PartialEq`/`Eq` compare only the `value`, each gated on the corresponding bound on `Value`. Two `Field` values are equal when their values are equal; the tag is a compile-time matter and plays no part in these runtime operations. - -Because the tag lives only in `PhantomData`, a `Field` carries the name purely at the type level. Code that needs the name reads it from the `Tag` parameter through trait resolution — for example matching a `Field` against a `HasField` bound — rather than from any stored data. - -## Examples - -`Field` appears most often inside the `HasFields` representation that a derive generates, where each struct field becomes one entry tagged by its `Symbol!` name: - -```rust -use cgp::prelude::*; - -#[derive(HasFields)] -pub struct Person { - pub name: String, - pub age: u8, -} - -// generated: -// impl HasFields for Person { -// type Fields = Product![ -// Field, -// Field, -// ]; -// } -``` - -A single `Field` can also be constructed directly from its value, with the tag supplied by the type annotation: - -```rust -use cgp::prelude::*; - -let name: Field = "Alice".to_string().into(); -assert_eq!(name.value, "Alice"); -``` - -For a tuple-struct field, the tag is an [`Index`](index.md) rather than a `Symbol!`, so the same wrapper names a positional field: `Field, u32>`. - -## Related constructs - -`Field` is the element type of the two structural spines: it fills a [`Product!`](../macros/product.md) (built from `Cons`/`Nil`, see [`cons.md`](./cons.md)) for a record and a [`Sum!`](../macros/sum.md) (built from `Either`/`Void`, see [`either.md`](./either.md)) for an enum. Its `Tag` is produced by [`Symbol!`](../macros/symbol.md) for a named field and by [`Index`](index.md) for a tuple-struct position. The list of `Field` entries for a whole type is assigned by [`#[derive(HasFields)]`](../derives/derive_has_fields.md) and surfaced through the [`HasFields`](../traits/has_fields.md) trait, while single-field access against a matching tag is the job of [`HasField`](../traits/has_field.md), built per field by [`#[derive(HasField)]`](../derives/derive_has_field.md). - -## Source - -- `Field` and its `From`, `Debug`, `PartialEq`, and `Eq` impls are defined in [crates/core/cgp-field/src/types/field.rs](../../../crates/core/cgp-field/src/types/field.rs). -- It is consumed by the field machinery: [`HasFields`](../traits/has_fields.md) in [crates/core/cgp-field/src/traits/has_fields.rs](../../../crates/core/cgp-field/src/traits/has_fields.rs), with the record/variant rebuilding traits in [crates/core/cgp-field/src/traits/from_fields.rs](../../../crates/core/cgp-field/src/traits/from_fields.rs) and [crates/core/cgp-field/src/traits/to_fields.rs](../../../crates/core/cgp-field/src/traits/to_fields.rs). -- The derive that emits `Product!`/`Sum!` lists of `Field` entries lives under [crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/derive_has_fields/). diff --git a/docs/reference/types/index.md b/docs/reference/types/index.md deleted file mode 100644 index f2145f46..00000000 --- a/docs/reference/types/index.md +++ /dev/null @@ -1,72 +0,0 @@ -# `Index` - -`Index` encodes a `usize` at the type level, giving a tuple-struct field a type-level name based on its position the way [`Symbol!`](../macros/symbol.md) names a field by its string. - -## Purpose - -`Index` exists because CGP's getter mechanism keys every field by a *type* tag, and a tuple-struct field has no string name to turn into a [`Symbol!`](../macros/symbol.md) — it has only a position. To make positional fields participate in the same trait-resolution machinery as named fields, the position itself must become a type. `Index` is that type: it carries a `usize` as a const-generic parameter and nothing else, so `Index<0>`, `Index<1>`, and `Index<2>` are three distinct types standing in for the first, second, and third fields of a tuple struct. - -Encoding the position as a type is what lets positional field access resolve through traits. Because `Index<0>` is a type, a context can carry a `HasField>` impl for its first field and a `HasField>` impl for its second side by side, and the compiler selects the right one purely from the tag — exactly as it would for two differently-named `Symbol!` tags. `Index` is therefore the numeric counterpart to `Symbol!`: a field is keyed by a `Symbol!` when it has a name and by an `Index` when it has only a position. - -`Index` carries no runtime data of its own; it is a zero-sized marker. Its sole job is to make a number available at the type level, so it can appear as a [`HasField`](../traits/has_field.md) tag, as the `Tag` of a [`Field`](field.md) entry inside a tuple struct's [`HasFields`](../traits/has_fields.md) list, and inside `PhantomData` wherever a positional name is needed at compile time. - -## Definition - -`Index` is a zero-sized struct parameterized only by a const-generic `usize`: - -```rust -#[derive(Eq, PartialEq, Clone, Copy, Default)] -pub struct Index; -``` - -The single const parameter `I` is the position the type represents — `Index<0>` for the field at offset zero, and so on. The struct has no fields, so a value of `Index` carries no data; the number lives entirely in the type. The derived `Default`, `Clone`, and `Copy` make a value trivially available when one is needed (for instance as a `PhantomData`-free tag value), and `Eq`/`PartialEq` compare two values of the same `Index` as always equal, since there is nothing to differ. - -`Index` implements both `Display` and `Debug`, and both print the underlying number `I` — `Index<0>` displays as `0`. The number a tag stands for is therefore visible directly in formatted output and in compiler diagnostics. - -## Behavior - -A tuple struct keys each of its fields by `Index`, counting from zero, so the field at position `N` is read through the tag `Index`. When a tuple struct derives [`HasField`](../derives/derive_has_field.md), the generated impl uses `Index<0>` for the `.0` field, `Index<1>` for `.1`, and so on, mapping each `get_field(PhantomData::>)` call to the corresponding positional access. The same `Index` tags then appear as the `Tag` of each [`Field`](field.md) entry in the tuple struct's [`HasFields`](../traits/has_fields.md) representation, so generic code walking the field list reads positions where it would read `Symbol!` names for a named struct. - -Because `Index` is zero-sized and the position lives in the type, accessing a field by index resolves entirely at compile time: there is no array bound check and no runtime indexing. Selecting the wrong index is a type error, not a runtime panic, because `Index<5>` on a three-field struct simply has no matching `HasField` impl. - -## Examples - -When a tuple struct derives `HasField`, each positional field is tagged by an `Index`: - -```rust -use cgp::prelude::*; - -pub struct Pair(pub u32, pub String); - -// generated for the first field: -// impl HasField> for Pair { -// type Value = u32; -// fn get_field(&self, _tag: PhantomData>) -> &u32 { -// &self.0 -// } -// } -``` - -A field can then be read by supplying the `Index` tag, and the chosen position is fixed at compile time: - -```rust -use cgp::prelude::*; - -let pair = Pair(7, "hi".to_string()); -assert_eq!(*pair.get_field(PhantomData::>), 7); -``` - -The number an `Index` carries is also visible through its `Display` impl, which prints the position: - -```rust -assert_eq!(Index::<2>.to_string(), "2"); -``` - -## Related constructs - -`Index` is the field-position half of CGP's tagging scheme; [`Symbol!`](../macros/symbol.md) is the field-name half, used for named struct fields and enum variants. The tags it produces are consumed by [`HasField`](../traits/has_field.md) for single-field access — built per field by [`#[derive(HasField)]`](../derives/derive_has_field.md) — and appear as the `Tag` of [`Field`](field.md) entries inside the [`HasFields`](../traits/has_fields.md) list of a tuple struct. Both `Index` and `Symbol!` encode a primitive at the type level: `Index` a `usize`, `Symbol!` a string. - -## Source - -- `Index` and its `Display` and `Debug` impls are defined in [crates/core/cgp-field/src/types/index.rs](../../../crates/core/cgp-field/src/types/index.rs). -- The `#[derive(HasField)]` codegen that tags tuple-struct fields with `Index` lives under [crates/macros/cgp-macro-core/src/types/cgp_data/](../../../crates/macros/cgp-macro-core/src/types/cgp_data/), and the `HasField` trait it targets is in [crates/core/cgp-field/src/traits/has_field.rs](../../../crates/core/cgp-field/src/traits/has_field.rs). diff --git a/docs/reference/types/life.md b/docs/reference/types/life.md deleted file mode 100644 index 0bf348aa..00000000 --- a/docs/reference/types/life.md +++ /dev/null @@ -1,59 +0,0 @@ -# `Life` - -`Life<'a>` is a zero-sized type that lifts a lifetime into a type, so that a lifetime parameter from a CGP trait can travel through machinery that only accepts types. - -## Purpose - -`Life` exists because CGP's wiring is parameterized by *types*, not lifetimes, yet a CGP trait may carry a lifetime generic of its own. The marker that surfaces a provider's dependencies, [`IsProviderFor`](../traits/is_provider_for.md), takes a tuple of the trait's generic parameters as a single type argument so that the compiler can match a provider against the exact instantiation it is asked for. A lifetime cannot appear directly in that tuple — a tuple is a type and its members must be types — so any lifetime parameter on the trait must first be turned into a type. `Life<'a>` is that conversion: it packages the lifetime `'a` as a concrete type that can sit alongside the trait's other type parameters. - -Without this lift, a consumer or provider trait that borrows — one declaring `fn get_reference(&self) -> &'a T` — could not record its `'a` in the dependency marker, and the wiring would be unable to distinguish one lifetime instantiation from another. `Life` lets the lifetime ride through `IsProviderFor` as `(Life<'a>, T)`, preserving it as part of the provider's identity while keeping the marker's argument a plain type. The macros insert `Life` automatically when generating provider traits for lifetime-carrying components; a user rarely writes it by hand but will see it in generated code and in compiler errors about provider resolution. - -## Definition - -`Life` is a tuple struct wrapping a single `PhantomData` over a raw pointer to a borrowed unit: - -```rust -pub struct Life<'a>(pub PhantomData<*mut &'a ()>); -``` - -The struct holds no runtime data — it is a zero-sized marker whose only job is to carry the lifetime `'a` in the type system. The choice of `PhantomData<*mut &'a ()>` for the phantom type is deliberate and controls how `Life<'a>` relates to other lifetimes under subtyping. A `*mut T` is *invariant* in `T`, so wrapping `&'a ()` behind a `*mut` makes `Life<'a>` invariant in `'a`: a `Life<'long>` is neither a subtype nor a supertype of a `Life<'short>`. Invariance is the correct choice here because the lifetime is being used as an exact identity in the dependency marker — two providers wired for different lifetimes must be treated as wired for genuinely different things, and a variant `Life` would let the compiler silently coerce one instantiation into another and pick the wrong provider. The raw pointer also keeps `Life<'a>` from carrying any auto-trait obligations tied to an actual borrow, since it does not own or reference a real value. - -## Behavior - -`Life` has no methods and implements no CGP traits of its own; its entire behavior is to occupy a type position. In a generated provider trait for a component with a lifetime, the lifetime is collected into the `IsProviderFor` argument tuple as `Life<'a>` so that the provider's dependency obligation reads the same way it would for any type parameter. The provider trait, its blanket forwarding impl, and the impls that satisfy it all agree on the same `(Life<'a>, T)` shape, which is what lets a borrowing component be wired and checked exactly like a non-borrowing one. - -## Examples - -`Life` appears in the wiring generated for a component whose consumer trait carries a lifetime. Given a borrowing getter component: - -```rust -use cgp::prelude::*; - -#[cgp_component(ReferenceGetter)] -pub trait HasReference<'a, T: 'a + ?Sized> { - fn get_reference(&self) -> &'a T; -} -``` - -the generated provider trait records the lifetime in its dependency marker through `Life`, so its `IsProviderFor` bound names the lifetime as the type `Life<'a>` rather than as a bare `'a`: - -```rust -// generated, in readable form: -// pub trait ReferenceGetter<'a, __Context__, T: 'a + ?Sized>: -// IsProviderFor, T)> -// { -// fn get_reference(__context__: &__Context__) -> &'a T; -// } -``` - -Every impl that wires this component — whether through `UseContext`, a `UseField` getter, or a hand-written provider — carries the same `(Life<'a>, T)` tuple, so the lifetime is preserved end to end through the resolution machinery. - -## Related constructs - -`Life` is consumed by [`IsProviderFor`](../traits/is_provider_for.md), whose final type argument is the tuple of a component's generic parameters and into which a lifetime parameter is lifted as `Life<'a>`. It is emitted by the component-defining macros, principally [`#[cgp_component]`](../macros/cgp_component.md), when a consumer trait declares a lifetime. Conceptually it sits alongside the other type-level markers CGP uses to make non-type things addressable in trait resolution — [`Index`](index.md) lifts a `usize` and [`Symbol`](chars.md) lifts a string the way `Life` lifts a lifetime. - -## Source - -- The type is defined in [crates/core/cgp-field/src/types/life.rs](../../../crates/core/cgp-field/src/types/life.rs). -- The macro logic that wraps a trait's lifetime parameters in `Life` when building the `IsProviderFor` argument tuple is in [crates/macros/cgp-macro-core/src/functions/is_provider_params.rs](../../../crates/macros/cgp-macro-core/src/functions/is_provider_params.rs), with related placement in [crates/macros/cgp-macro-core/src/types/empty_struct.rs](../../../crates/macros/cgp-macro-core/src/types/empty_struct.rs) and [crates/macros/cgp-macro-core/src/types/cgp_provider/provider_impl_args.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_provider/provider_impl_args.rs). -- For how it is generated and the index of tests, see the implementation document [implementation/functions/parse/is_provider_params.md](../../implementation/functions/parse/is_provider_params.md). diff --git a/docs/reference/types/mref.md b/docs/reference/types/mref.md deleted file mode 100644 index 3e4d1ef6..00000000 --- a/docs/reference/types/mref.md +++ /dev/null @@ -1,61 +0,0 @@ -# `MRef` - -`MRef<'a, T>` is a "maybe-reference" — an enum that holds either a borrow of a `T` or an owned `T` — so a getter can return whichever it has without committing every implementor to one or the other. - -## Purpose - -`MRef` exists to let a single getter signature accommodate both the context that already stores a value and the context that must produce one on the fly. A getter that returns `&'a T` forces every context to keep a `T` it can lend out; a getter that returns `T` forces every context to hand over ownership, cloning even when it has a perfectly good reference to share. `MRef<'a, T>` removes that dilemma by being either case at runtime: a context with the value in a field returns `MRef::Ref` and lends it, while a context that computes or assembles the value returns `MRef::Owned` and gives it away. The caller treats both uniformly because `MRef` derefs to `T`. - -The type earns its keep in CGP's getter machinery, where the return type chosen for a getter method decides what body the macro generates. When a getter is declared to return `MRef<'a, T>` and takes `&self`, the generated field accessor wraps the borrowed field as `MRef::Ref(...)`, so the common case — reading a stored field — costs nothing extra, while the same interface still permits a provider elsewhere to return an owned value. This is what lets a getter abstract over "do I have this value, or do I make it?" without splitting into two traits. - -## Definition - -`MRef` is a two-variant enum parameterized by a lifetime and an element type: - -```rust -pub enum MRef<'a, T> { - Ref(&'a T), - Owned(T), -} -``` - -The `Ref` variant borrows a `T` for the lifetime `'a`; the `Owned` variant carries a `T` by value. The lifetime applies only to the borrowed case, so an `MRef` built from an owned value is effectively unbounded in `'a`. The enum is an ordinary owned value — there is nothing type-level about it — and it is the runtime payload a getter passes back to its caller. - -## Behavior - -`MRef` behaves like a smart pointer to `T`, which is what makes the two variants interchangeable at the call site. It implements `Deref` by matching on the variant and returning a `&T` either way, so `&*my_ref` and any method call that auto-derefs work regardless of which case is inside. It also implements `AsRef` over the same logic, giving an explicit `as_ref()` for code that prefers it. - -Constructing an `MRef` is frictionless because it implements `From` in both directions: `From` builds the `Owned` variant and `From<&'a T>` builds the `Ref` variant, so a value or a reference converts with `.into()`. When a caller needs to take ownership unconditionally, `get_or_clone` resolves the enum to a plain `T` — returning the owned value as is, or cloning the borrowed one — and is available whenever `T: Clone`. These three pieces — transparent `Deref`/`AsRef`, the two `From` impls, and `get_or_clone` — are the whole surface; a borrowed `MRef` is read cheaply and promoted to ownership only when explicitly asked. - -## Examples - -`MRef` is used as the return type of a getter that should work whether the context stores the value or produces it. The following getter reads a borrowed field and hands it back as a borrowing `MRef`: - -```rust -use cgp::prelude::*; - -let stored = String::from("hello"); - -// a context lending a stored value: -let borrowed: MRef<'_, String> = MRef::from(&stored); -assert_eq!(&*borrowed, "hello"); - -// a provider returning a freshly built value through the same type: -let made: MRef<'_, String> = MRef::from(String::from("world")); -assert_eq!(made.as_ref(), "world"); - -// promote either to an owned value when ownership is required: -let owned: String = borrowed.get_or_clone(); -assert_eq!(owned, "hello"); -``` - -Both `borrowed` and `made` have the same type and are consumed the same way; only the construction differs, and `get_or_clone` clones the borrowed case while moving the owned one. - -## Related constructs - -`MRef` is one of the getter return modes recognized by the field-getter macros: a getter declared to return `MRef<'a, T>` over `&self` generates a borrowing accessor, parallel to how returning `&T`, `Option<&T>`, or `&str` selects other accessor shapes. It is therefore commonly seen with [`#[cgp_getter]`](../macros/cgp_getter.md) and the [`HasField`](../traits/has_field.md) access it builds on, and with the [`UseField`](../providers/use_field.md) provider that wires those getters. Its lifetime is an ordinary borrow lifetime and is unrelated to the type-level lifetime lift [`Life`](life.md), which serves a different purpose in provider wiring. - -## Source - -- The type is defined in [crates/core/cgp-field/src/types/mref.rs](../../../crates/core/cgp-field/src/types/mref.rs), including its `Deref`, `AsRef`, the two `From` impls, and `get_or_clone`. -- The macro logic that recognizes an `MRef<'a, T>` getter return type is in [crates/macros/cgp-macro-core/src/functions/field/parse.rs](../../../crates/macros/cgp-macro-core/src/functions/field/parse.rs) (the `MRef` field mode), and the `MRef::Ref(...)` body is emitted by the shared field-mode conversion in [crates/macros/cgp-macro-core/src/types/getter/field_mode.rs](../../../crates/macros/cgp-macro-core/src/types/getter/field_mode.rs), which fully-qualifies `MRef` through the `crate::exports` markers. diff --git a/docs/reference/types/path_cons.md b/docs/reference/types/path_cons.md deleted file mode 100644 index 3cf68113..00000000 --- a/docs/reference/types/path_cons.md +++ /dev/null @@ -1,84 +0,0 @@ -# `PathCons` - -`PathCons` is the type-level path spine — a recursive list of segments, where both the head and the tail may be unsized — that CGP uses to address an entry deep inside a delegation table. - -## Purpose - -`PathCons` exists to express a *route* through nested delegation tables as a single type. Where a bare component name picks one entry out of a context's table, CGP sometimes needs to point at an entry that lives behind one or more layers of indirection — inside a namespace, behind another namespace it inherits from, under a prefix. A path is the type that names such a route: a list of segments read left to right, each segment narrowing the lookup one step further. `PathCons` is the cons cell of that list, and [`Nil`](cons.md) terminates it, so `PathCons>` is the two-step path "first `A`, then `B`." - -A path is a distinct spine from the [`Cons`](cons.md) product list even though both are right-nested and `Nil`-terminated, and the difference is the unsized bound. `PathCons` declares `Head: ?Sized` and `Tail: ?Sized`, which lets a path segment be a trait object or other unsized type and lets the whole path be manipulated without requiring its parts to have a known size. A product list keys a struct's fields and its elements are always sized values; a path keys a lookup and its segments are pure type-level markers that never need to be `Sized`. - -The segments themselves are the same markers CGP uses elsewhere: a lowercase dotted name becomes a [`Symbol`](chars.md) type-level string, and a capitalized name becomes that named type (typically a component key such as `FooProviderComponent` or a namespace marker). A path is therefore an interleaving of symbols and component names — the form `@a.B.c` — assembled into a `PathCons` chain. Paths are written with the [`Path!`](../macros/path.md) macro rather than spelled by hand, so this document describes the runtime spine; the `@`-segment syntax and the expansion live in that macro's document. - -## Definition - -`PathCons` is a zero-sized struct holding two `PhantomData` markers, one for the head segment and one for the rest of the path: - -```rust -pub struct PathCons(pub PhantomData, pub PhantomData); -``` - -The `Head` is the first segment of the path and the `Tail` is the remainder, expected to be either another `PathCons` or `Nil` at the end. Both bounds are `?Sized` so that any type — sized or not — can occupy a segment. The struct carries no runtime data; like the other type-level building blocks it exists purely so that a route can be named and matched in trait resolution. - -## Behavior - -`PathCons` participates in path concatenation through the [`ConcatPath`](../traits/static_format.md) trait, which appends one path onto the end of another at the type level. The trait recurses down the spine: `PathCons` concatenates with `Other` by keeping `Head` and concatenating `Tail` with `Other`, while `Nil` concatenates with `Other` by simply becoming `Other`. The result is the expected behavior of list append, computed entirely as an associated-type projection: - -```rust -pub trait ConcatPath { - type Output: ?Sized; -} - -impl ConcatPath for PathCons -where - Tail: ConcatPath, -{ - type Output = PathCons>::Output>; -} - -impl ConcatPath for Nil { - type Output = Other; -} -``` - -Beyond concatenation, a `PathCons` path is consumed by [`RedirectLookup`](../providers/redirect_lookup.md), the provider that resolves a delegation by walking a context's table along a path. When a namespace or a prefixed component re-routes a lookup, it does so by producing a `RedirectLookup` whose `Path` is a `PathCons` chain; `RedirectLookup` follows the chain segment by segment until it lands on a concrete provider. The path itself never names a provider — it only describes where to look — so the same path can resolve to different providers depending on the table it is walked against. - -## Examples - -Paths are produced by the [`Path!`](../macros/path.md) macro and most often appear inside the wirings emitted by [`#[cgp_namespace]`](../macros/cgp_namespace.md). A namespace entry that redirects one component key to a path desugars into a `RedirectLookup` over a `PathCons` chain: - -```rust -use cgp::prelude::*; - -cgp_namespace! { - new MyNamespace { - FooProviderComponent => - @MyFooComponent, - } -} - -// the emitted entry, in readable form: -// impl<__Table__> MyNamespace<__Table__> for FooProviderComponent { -// type Delegate = RedirectLookup<__Table__, PathCons>; -// } -``` - -A path with both a lowercase symbol segment and a capitalized component segment interleaves the two marker kinds. Registering a component into a namespace under a prefix produces a two-segment path: - -```rust -// @MyBarComponent.BarProviderComponent expands to -// PathCons> -``` - -Here the lookup steps first through `MyBarComponent` and then through `BarProviderComponent` before resolving. A single-segment path is `PathCons`, and the empty path is `Nil` alone. - -## Related constructs - -`PathCons` is the routing counterpart to the product spine [`Cons`](cons.md)/`Nil`; it shares the right-nested, `Nil`-terminated shape but its segments are `?Sized` markers rather than sized field values. Its segments are [`Symbol`](chars.md) type-level strings (for lowercase names) and named component or namespace types (for capitalized names). Paths are built by the [`Path!`](../macros/path.md) macro, appended through [`ConcatPath`](../traits/static_format.md), and walked by [`RedirectLookup`](../providers/redirect_lookup.md) when resolving a delegation. They are produced throughout [`#[cgp_namespace]`](../macros/cgp_namespace.md), which uses them to reroute namespace entries and to register prefixed components. - -## Source - -- The runtime type is defined in [crates/core/cgp-base-types/src/types/path.rs](../../../crates/core/cgp-base-types/src/types/path.rs) (`PathCons`), with `Nil` in [crates/core/cgp-base-types/src/types/nil.rs](../../../crates/core/cgp-base-types/src/types/nil.rs). -- The `ConcatPath` trait and its impls are in [crates/core/cgp-base-types/src/traits/concat_path.rs](../../../crates/core/cgp-base-types/src/traits/concat_path.rs). -- The constructing macro is [`Path!`](../macros/path.md) ([crates/macros/cgp-macro-lib/src/path.rs](../../../crates/macros/cgp-macro-lib/src/path.rs)), whose fold over the segments lives in [crates/macros/cgp-macro-core/src/types/path/unipath.rs](../../../crates/macros/cgp-macro-core/src/types/path/unipath.rs). -- `RedirectLookup`, which consumes a path at resolution time, is in [crates/core/cgp-component/src/providers/redirect_lookup.rs](../../../crates/core/cgp-component/src/providers/redirect_lookup.rs). diff --git a/docs/related-work/AGENTS.md b/docs/related-work/AGENTS.md deleted file mode 100644 index a1a62625..00000000 --- a/docs/related-work/AGENTS.md +++ /dev/null @@ -1,51 +0,0 @@ -# AGENTS.md - -This file provides guidance to LLM agents when working with code in this repository. - -This directory holds the **related-work** documents of the CGP knowledge base — one document per external concept, framework, or language feature that solves a problem CGP also solves, or that resembles a CGP construct closely enough that a reader coming from it can be met on familiar ground. Read the knowledge-base [README.md](../README.md) for the background on the whole base, and the governing [../AGENTS.md](../AGENTS.md) for the rules every section shares. The rules below are specific to related work. - -## Why this section exists - -A related-work document exists to serve *future user-facing documentation*, not to teach CGP directly. The rest of the knowledge base explains CGP on its own terms; this section records how a mainstream idea — dependency injection, implicit parameters, type classes, and so on — actually works, what its users value and resent about it, and where CGP lands relative to it. An agent later asked to write a tutorial, a blog post, or a landing page *for readers who already know that idea* reads the matching related-work document first, then leans on the reader's existing intuition to make the CGP explanation land. The audience of the eventual writing is a practitioner of the related concept; the audience of the document itself is the agent preparing to address them. - -This purpose sets the bar for the content. A related-work document is worth writing only if it captures the concept faithfully enough that an agent could explain it to that concept's own community without embarrassment, and honestly enough that the comparison to CGP survives a skeptic who prefers the other tool. Shallow praise of CGP and strawman versions of the related work both defeat the point: the reader we are ultimately writing for will spot either one immediately. - -## What every related-work document must cover - -Each document explains one related concept in genuine depth and then positions CGP against it. The explanation comes first and stands on its own — a reader learns the related work here, not just a caricature of it — and the CGP comparison follows once the concept is on the table. Every document works through the same obligations: - -- **Explain the concept in real detail.** Cover the important sub-concepts of the related work, not just its headline. Define its vocabulary, show how it is actually used, and explain the mechanism behind it, at the depth a practitioner of it would recognize as correct. -- **Show it in code.** Demonstrate each important sub-concept with a code snippet in the related work's own language and idiom, and — where CGP has a counterpart — show the same thing written in CGP right beside it, so the two can be read against each other. Not every snippet has a CGP equivalent; say so when it does not. -- **Give both the pros and the cons.** State plainly what users *like* about the concept and what they *dislike* — the pain points, the footguns, the recurring complaints — drawing on real community sentiment rather than invented objections. A document that lists only weaknesses is as useless as one that lists only strengths. -- **Compare how CGP solves the problem differently.** Explain where CGP takes the same approach, where it diverges, and *why* — what CGP's design buys and what it costs relative to the related work. Be fair: name the cases where the related work is the better fit. -- **Analyze how to present CGP positively to someone who knows the concept.** Close with concrete guidance for the future writer: which of the reader's existing intuitions to build on, which analogies land and which mislead, which CGP advantages will resonate with this particular audience, and which of their expectations CGP will violate and must therefore address head-on. This positioning analysis is the section the rest of the document exists to support. - -## Sourcing and citations - -**Unlike the [examples](../examples/) directory, related-work documents cite their sources.** An example is re-derived as native knowledge-base material with no pointer to where the idea came from; a related-work document is the opposite — its credibility rests on being a faithful account of an external thing, so it must be traceable to that thing. Research the concept from its authoritative documentation, primary references, and representative community discussion before writing, and record what you used. - -Study the research literature directly when the concept comes out of programming-language research, as row polymorphism, type classes, effect systems, and their kin do. For these ideas the online resources to study are not only the language documentation that popularized them but the academic papers and the serious technical articles that define and analyze them: read the foundational paper a feature descends from and, where it exists, the current research that generalizes or corrects it, alongside the blog posts and talks that make the theory legible. The primary literature is where a PL concept's precise definition, formal properties, and design space actually live — often stated nowhere else — so treat it as a first-class source to study and to cite, not an optional supplement to the documentation. - -Ground every factual claim about the related work in a real source. Cite official language or framework documentation for how a feature behaves, primary papers for a concept's origin or formal properties, and reputable community writing for sentiment about what users like or dislike — attributing opinion as opinion, not as fact. Collect the citations in a **Sources** section at the end of the document, as a framed list of links with a short note on what each supports, and reference them inline where a specific claim leans on one. Do not invent quotations, statistics, or version-specific behavior; when unsure whether a detail is current, verify it against the source rather than trusting memory. - -Keep the account current and neutral. Describe the related work as it exists now, note the version or edition when behavior is version-specific (Scala 2 `implicit` versus Scala 3 `given`/`using`, for instance), and represent the concept as its proponents would recognize it before critiquing it. - -## The CGP side must obey the synchronization rule - -Every CGP snippet in a related-work document is bound by the [synchronization rule](../AGENTS.md) exactly as a reference document's Expansion section is. A CGP comparison that shows syntax the macros no longer accept, or an expansion the code no longer produces, is a bug in the change that made it stale — and a especially damaging one here, because it will be quoted into user-facing material and shown to the very audience most likely to scrutinize it. Verify each CGP snippet against the source and the current macro behavior, invoke the `/cgp` skill before writing any CGP code, and prefer the modern idioms the skill and the [guides](../guides/) recommend. Draw CGP snippets from the [examples](../examples/) and the running scenarios the rest of the base already uses, rather than inventing fresh contexts, so the CGP side of every comparison speaks the knowledge base's shared vocabulary. - -## Document structure - -Each related-work document follows the same shape so readers can navigate any of them by habit. Open with a level-one heading naming the concept and a one-sentence summary of what it is and why it is worth comparing to CGP. Then proceed through these sections, using the same headings: - -- **Purpose** — what problem the concept solves, and why a CGP reader should care that it exists. One or two paragraphs of framing. -- **The concept in depth** — the faithful explanation of the related work: its sub-concepts, its vocabulary, and its mechanism, each illustrated with a code snippet in the related work's own language. This is the section that must satisfy a practitioner of the concept. Use titled subsections when the concept has several distinct parts. -- **How CGP expresses it** — the same problems written in CGP, shown against the related-work snippets above, with prose explaining where the two align and where they part ways. -- **What users like and dislike** — the honest pros and cons of the related work, drawn from real sentiment and cited. -- **How CGP compares** — the design-level comparison: what CGP's approach buys, what it costs, and where the related work remains the better choice. -- **Presenting CGP to someone who knows this** — the positioning guidance for future user-facing writing: intuitions to build on, analogies that land or mislead, advantages that resonate, and expectations to address before they trip the reader. -- **Sources** — the framed list of citations described above. - -Follow the dual-reader prose style (the `/dual-reader-prose` skill) throughout: open every section with a self-contained topic sentence, frame every list, and let the prose carry the meaning around each code block. Prefer plain language and the knowledge base's established CGP vocabulary — consumer trait, provider trait, provider, wiring, impl-side dependency, context — so a reader moving between this section and the rest never reconciles two dialects. - -Register every new document in the catalog in [README.md](README.md) in the same change that adds it, and cross-link generously: to the [concepts](../concepts/README.md) for the CGP idea a comparison rests on, to the [reference](../reference/README.md) for the exact syntax of any construct shown, and to sibling related-work documents when two concepts are themselves related. diff --git a/docs/related-work/README.md b/docs/related-work/README.md deleted file mode 100644 index 036618a5..00000000 --- a/docs/related-work/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# CGP Related Work - -This directory compares CGP to the outside ideas it resembles — one document per external concept, framework, or language feature that solves a problem CGP also solves, or that a reader might already know well enough to learn CGP through. Each document explains the related work faithfully and in depth, weighs what its users like and dislike about it, and then positions CGP against it. The documents exist to make CGP legible to readers who arrive with an existing mental model, by meeting that model on its own terms before showing where CGP diverges. - -## Why this exists - -Related-work documents serve *future user-facing documentation*, not the reader trying to learn CGP directly. When an agent later writes a tutorial, an article, or a landing page aimed at people who already know dependency injection or implicit parameters, it reads the matching document here first, then builds the CGP explanation on the intuitions those readers already hold. The eventual audience is a practitioner of the related concept; the audience of the document itself is the agent preparing to address them, and its job is to supply the honest comparison and the positioning strategy that writing will rest on. - -Honesty is the whole value of the section. A document that flatters CGP or strawmans the related work is worse than useless, because the readers it ultimately serves are the people most able to see through both. So each document explains the related work well enough that its own community would recognize the account, states the genuine pros and cons of both sides, and names the cases where the related work is simply the better tool. The [AGENTS.md](AGENTS.md) in this directory carries the authoring rules — chiefly the obligations every document must meet, and the one rule that sets this section apart from the rest of the knowledge base: unlike examples, related-work documents cite their sources. - -## How related work differs from the other sections - -Related work answers a question none of the other sections do: *how does CGP relate to the idea I already know?* A [reference document](../reference/README.md) explains one CGP construct completely; a [concept document](../concepts/README.md) explains one cross-cutting CGP idea; an [example](../examples/README.md) develops one use case end to end; a [guide](../guides/README.md) directs a choice between CGP constructs. All four look inward at CGP. A related-work document looks outward — it takes something from outside CGP as the subject, explains it on its own terms, and uses CGP only as the point of comparison. Where an example is re-derived as native material with no pointer to its origin, a related-work document is the opposite: its credibility depends on being a traceable, cited account of the external thing. - -The two therefore lean on the rest of the base rather than restating it. A related-work document links to the [concepts](../concepts/README.md) for the CGP idea a comparison rests on and to the [reference](../reference/README.md) for the exact syntax of any construct it shows, keeping its own focus on the external concept and the comparison. When a CGP snippet is needed, it is drawn from the running scenarios the [examples](../examples/README.md) already use, so the CGP side of every comparison speaks the knowledge base's shared vocabulary. - -## The catalog - -Each document below names an external concept and compares it to CGP; the authoring rules for adding one live in [AGENTS.md](AGENTS.md). - -- [Dependency injection](dependency-injection.md) — the IoC-container and constructor-injection model of Spring, Guice, Dagger, and their kin, and how CGP's impl-side dependencies and per-context wiring inject dependencies without a container, reflection, or runtime graph. -- [Implicit parameters](implicit-parameters.md) — the implicitly-passed context arguments of Scala's `given`/`using` and Haskell's `ImplicitParams`, alongside the type-class resolution both build on, and how CGP's implicit arguments and context-threaded wiring achieve the same context propagation while trading global coherence for per-context choice. -- [Row polymorphism, structural typing, and extensible data types](row-polymorphism.md) — the field-and-variant-shape–driven typing of PureScript's rows, OCaml's polymorphic variants, and the languages around them, framed by Morris and McKinna's "rows by any other name" row-theory account, and how CGP brings the same extensible records and variants to nominal Rust through derived type-level shapes rather than a built-in row kind. -- [Algebraic effects and handlers](algebraic-effects.md) — the operations-and-handlers model of Koka, OCaml, Flix, and Eff, where a handler interprets an effect by capturing the continuation and resuming it zero, one, or many times, and how CGP reproduces the effect/handler split but only for the exactly-once fragment that the literature identifies as dynamic binding — resolved statically per context rather than dynamically down a call stack, and extended to configuring abstract types. -- [ML modules and modular implicits](ml-modules.md) — the signatures, structures, and functors of OCaml and Standard ML, and the modular-implicits extension that adds type-directed resolution over them, framed by Dreyer, Harper, and Chakravarty's "modular type classes," and how CGP maps components to signatures, providers to structures, and higher-order providers to functors while replacing manual, ordered functor application with a declarative `delegate_components!` table resolved per context. -- [Type classes](type-classes.md) — the principled ad-hoc polymorphism of Haskell, Agda, and Lean, dictionary-passing and coherence, the overlapping- and incoherent-instance extensions that strain against it, the diamond problem in dependently-typed settings, and Dreyer, Harper, and Chakravarty's "modular type classes," and how CGP is a type-class system that removes global coherence and replaces implicit resolution with explicit per-context selection — making overlapping and incoherent instances ordinary and deterministic rather than exceptional and dangerous. -- [Dynamic dispatch, dynamic typing, and prototypal inheritance](dynamic-dispatch.md) — the runtime mechanisms of dynamically-typed and object-oriented languages: late binding and message passing, the vtables and method dictionaries that implement them, duck typing, and prototype-based delegation in Self, JavaScript, and Lua, and how CGP reproduces their openness — many implementations behind one interface, behavior shared by delegation with `self` staying bound, defaults inherited and overridden, code that reads as if it just sends messages — while resolving all of it statically, so its vtable is a type-level table erased before runtime and its prototype chain is walked by the compiler. -- [Reflection and compile-time introspection](reflection.md) — full treatments of Bevy's runtime reflection, Zig's `comptime` as the compile-time model, and Rust's nightly reflection MVP (tracked from its source, tracking issues, and PRs: `core::mem::type_info`, the `TypeKind`/`Struct`/`Field` API, the `#[rustc_comptime]` restriction, and the open road to an RFC), set against Go, Java, C++26, D, and facet, and how CGP reaches the same generic-over-structure payoff by encoding a type's shape as type-level lists the trait system resolves against — shown through `cgp-serde`'s `SerializeFields` and compared with facet and the reflection MVP, carrying field types as types so it recurses into them, checked when written, and extended to behavior and abstract-type selection. diff --git a/docs/related-work/algebraic-effects.md b/docs/related-work/algebraic-effects.md deleted file mode 100644 index f19a25b9..00000000 --- a/docs/related-work/algebraic-effects.md +++ /dev/null @@ -1,209 +0,0 @@ -# Algebraic effects and handlers - -Algebraic effects and handlers are a way to define side-effecting *operations* — throwing, reading state, yielding, awaiting — separately from the *handlers* that interpret them, so that one piece of effectful code can be run under many different interpretations, with a handler free to capture the rest of the computation as a continuation and resume it zero, one, or many times. CGP shares the split between an operation and its interpretation, and even the idea of choosing the interpretation from the surrounding context, but it keeps only the fragment where the continuation is used exactly once and in place — which turns out to be dynamic binding, resolved statically per context rather than dynamically down a call stack. - -## Purpose - -Algebraic effects solve the problem of writing code that *does* something effectful without fixing *how* that effect is carried out. A function that needs to read a configuration value, log a message, throw an error, suspend for I/O, or make a nondeterministic choice normally has to commit to a concrete mechanism — a global, a `Result`, a monad, a callback — and that commitment leaks into its type and forces every caller to accommodate it. Algebraic effects let the function instead *perform an operation* named by an abstract effect, and defer the meaning of that operation to a *handler* installed somewhere up the call stack. The same code runs against a real logger in production, a collector in a test, and a no-op in a benchmark, with only the handler changing. - -This is the same separation CGP draws between a [consumer trait and its providers](../concepts/consumer-and-provider-traits.md), which is why the comparison is worth drawing carefully rather than casually. Both paradigms take a capability, expose it as an interface a caller invokes without naming an implementation, and supply the implementation from the surroundings. Where they diverge is *what a handler is allowed to do* and *how the surroundings choose it*: an effect handler receives the continuation and wields real control-flow power, chosen by dynamic scope; a CGP provider is an ordinary function selected by the context's type at compile time. Showing a reader where that line falls — and why CGP sits on the side of it that coincides with dynamic binding — is the heart of this comparison, and it draws on the [row-polymorphism](row-polymorphism.md) account too, since the effect systems that type these operations do so with the very row types that document covers. - -## The concept in depth - -Algebraic effects come in layers that a reader should keep distinct: the *operations* that name an effect, the *equational theory* that classically justifies calling it "algebraic," the *handlers* that interpret operations by grabbing the continuation, the *effect system* that types which operations a computation may perform, and the concrete language designs — Koka, OCaml, Flix, Eff — that realize these to different degrees. The subsections below build up in that order, and the final one isolates the single fragment that CGP corresponds to. - -### Operations, effects, and the equational theory - -An *effect* is a signature of *operations*, and a computation produces the effect by *performing* one of them. The founding idea, due to Gordon Plotkin and John Power, is that impure behavior arises from a set of operations — `get` and `put` for mutable state, `read` and `print` for I/O, `raise` for exceptions — rather than from an opaque notion of "side effect" ([Plotkin & Pretnar, *Handling Algebraic Effects*](https://homepages.inf.ed.ac.uk/gdp/publications/handling-algebraic-effects.pdf)). What made the account *algebraic* is that each effect came with an *equational theory*: laws the operations obey, such as the state laws relating `get` and `put`, whose free model induces exactly the monad for that effect ([Plotkin & Pretnar 2013](https://lmcs.episciences.org/705); [Bauer & Pretnar, *Programming with Algebraic Effects and Handlers*](https://www.researchgate.net/publication/221671686_Programming_with_Algebraic_Effects_and_Handlers)). This theoretical grounding is where the name comes from, but it is largely vestigial in the practical languages below, which keep the operations-and-handlers structure and drop the laws — a point worth holding onto, because CGP keeps even less of the algebra than they do. - -### Handlers and the continuation - -A *handler* interprets the operations of an effect, and its defining power is that it receives the *continuation* — the suspended rest of the computation from the point the operation was performed. When a computation performs `op(arg)`, control transfers to the nearest enclosing handler for that operation, which receives both the argument and the continuation as a first-class, delimited resumption ([Pretnar, *An Introduction to Algebraic Effects and Handlers*](https://www.eff-lang.org/handlers-tutorial.pdf)). This generalizes an exception handler in one decisive way: an exception handler can only *abandon* the computation, whereas an effect handler can *resume* it. Plotkin and Pretnar gave this its semantics — a handler is a *model* of the effect's theory, and handling is the unique homomorphism from the free model into it ([Plotkin & Pretnar, *Handlers of Algebraic Effects*](https://homepages.inf.ed.ac.uk/gdp/publications/Effect_Handlers.pdf)). - -How many times a handler invokes the continuation is what determines the effect it realizes, and the three cases are worth naming because they mark exactly where CGP can and cannot follow: - -- **Zero times** — the handler discards the continuation and returns its own value, which is *exception* behavior: `raise` aborts to the handler and never comes back. -- **Once** — the handler resumes the continuation with a result, which is the *ordinary* case: reading state, dynamic binding, logging, a normal function-like call that yields a value and lets the caller carry on. -- **Many times** — the handler resumes the continuation more than once, which is what makes effects *powerful*: nondeterminism and backtracking (try both branches), generators (yield and later resume), cooperative scheduling and async/await (suspend, run something else, resume). This is *multi-shot* resumption, and it is impossible to express as an ordinary function return. - -### Effect typing: rows, sets, or nothing - -An *effect system* tracks, in a computation's type, which effects it may perform, so that unhandled effects can be caught statically — and languages differ sharply in whether they do this. Koka types effects as a *row* — `` in a signature means the function may throw and diverge — building directly on row polymorphism ([Leijen, *Algebraic Effects for Functional Programming*](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/08/algeff-tr-2016-v2.pdf)); this is the same row machinery surveyed in [row polymorphism](row-polymorphism.md), applied to effects instead of records. Flix types effects as a *set* over a lattice and uses the result for *purity reflection*, letting the compiler know when code is pure enough to parallelize or eliminate ([Madsen et al., *Programming with Purity Reflection*](https://drops.dagstuhl.de/entities/document/10.4230/LIPIcs.ECOOP.2023.18)). At the other extreme, Eff and OCaml 5 deliberately do *not* track effects in types at all; an unhandled effect is a runtime failure rather than a type error ([OCaml manual, *Effect handlers*](https://ocaml.org/manual/5.5/effects.html)). The presence or absence of this static check is the sharpest axis of variation among the languages, and the one where CGP's `check_components!` has something precise to say. - -### The concept in three languages - -The same effect looks recognizably similar across the languages that implement it, which makes their differences legible. In **Koka**, an effect declares operations that are marked `ctl` (full control, resume any number of times), `fun` (tail-resumptive — resume exactly once, in place), or `val`; the effect appears as a row in the type, and a handler is installed with `with`: - -```koka -effect ask - fun ask() : int // `fun`: tail-resumptive, resumes exactly once - -fun add-twice() : ask int - ask() + ask() - -fun main() : console () - with fun ask() 21 // install a handler that supplies 21 - println( add-twice() ) // 42 -``` - -A `ctl` operation is the multi-shot form — a `ctl flip()` handler may call `resume(True)` *and* `resume(False)`, resuming the continuation twice to explore both branches — and if an effect is never handled, Koka keeps it in the row and the type checker demands the enclosing function declare it, so an unhandled effect is ultimately a *type error*. - -In **OCaml 5**, effects are declared by extending `Effect.t`, performed with `perform`, and handled by matching on the effect and its continuation `k`, which is resumed with `continue`: - -```ocaml -type _ Effect.t += Ask : int Effect.t - -let add_twice () = perform Ask + perform Ask - -let () = - let open Effect.Deep in - try_with add_twice () - { effc = fun (type a) (eff : a Effect.t) -> - match eff with - | Ask -> Some (fun (k : (a, _) continuation) -> continue k 21) - | _ -> None } -``` - -OCaml's continuations are **one-shot**: `k` may be resumed *at most once*, enforced by a dynamic check that raises `Continuation_already_resumed` on a second resume, a restriction chosen because one-shot continuations are far cheaper and suffice for the concurrency schedulers the feature was built for ([Sivaramakrishnan et al., *Retrofitting Effect Handlers onto OCaml*](https://kcsrk.info/slides/handlers_edinburgh.pdf)). OCaml tracks **no** effects in types, so a `perform` with no matching handler raises `Effect.Unhandled` at runtime. - -In **Flix**, an effect is declared with `eff`, appears in a function type after a backslash, and is handled with `run … with handler`: - -```flix -eff Ask { - def ask(): Int32 -} - -def addTwice(): Int32 \ Ask = - Ask.ask() + Ask.ask() - -def main(): Int32 = - run { - addTwice() - } with handler Ask { - def ask(k) = k(21) - } -``` - -Flix's set-based effect system means `\ Ask` is part of `addTwice`'s type, and the handler's continuation `k` makes the resume explicit, just as Koka's `resume` and OCaml's `continue` do. - -### The fragment CGP corresponds to: tail-resumptive handlers *are* dynamic binding - -The single fact that makes the CGP comparison precise is that a handler which resumes the continuation *exactly once, in tail position* is no longer doing anything control-theoretic — it is dynamic binding. A handler is *tail-resumptive* when every operation clause invokes the continuation in tail position, and the literature names its canonical example outright: **the canonical tail-resumptive handler is dynamic binding** ([Xie et al., *Effect Handlers, Evidently*](https://www.microsoft.com/en-us/research/wp-content/uploads/2020/07/evidently-5f0b7dbc1a998.pdf)). The reason this matters for implementation, and for CGP, is that such a handler never needs to capture the continuation at all: the compiler can run the operation *in place* on the current stack, replacing the dynamic search for a handler with a constant-offset lookup into an *evidence vector* of handlers passed down like a dictionary ([Xie & Leijen, *Generalized Evidence Passing for Effect Handlers*](https://xnning.github.io/papers/multip.pdf)). In other words, the efficient compilation of the exactly-once fragment of effect handlers *is* dictionary passing. That is the fragment CGP occupies — and it occupies it directly, with no dynamic search underneath and nothing else on top. - -## How CGP expresses it - -CGP reproduces the operations-and-handlers structure with its consumer/provider split, but every CGP "handler" is an ordinary function that returns once, so CGP realizes only the exactly-once fragment above. A [component](../reference/macros/cgp_component.md) is the effect signature, a [provider](../reference/macros/cgp_impl.md) is the handler, and [wiring](../reference/macros/delegate_components.md) installs the handlers on a context. The correspondence is exact for the tail-resumptive case and breaks cleanly wherever an effect would reach for the continuation. - -### Components are effect signatures; providers are (tail-resumptive) handlers - -A CGP component declares operations the way an effect declares them, and a provider interprets them the way a handler does — with the standing restriction that the interpretation is a plain function body. Declaring a component names a capability without giving it meaning: - -```rust -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} -``` - -`CanGreet` is the effect signature and `greet` the operation; a provider written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) is the handler, interpreting the operation for any context. The structural match is one-to-one — signature, operation, handler, installation — but the provider's body is not handed a continuation and cannot choose whether to resume. It computes a value and returns, and the caller resumes in place, exactly once. In effect-handler terms every CGP provider is a `fun` operation in Koka's sense, never a `ctl` one; there is no `resume` to call zero times or twice, because there is no reified continuation at all. - -### Reading from the context is dynamic binding, exactly - -The place the correspondence is not merely structural but *exact* is context-value access, because that is dynamic binding on both sides. Koka's `with fun ask() 21` installs a tail-resumptive handler that supplies a value to deeply nested code without threading it through every call; CGP's [getters](../concepts/implicit-arguments.md) and [`#[implicit]` arguments](../reference/attributes/implicit.md) do the same by reading a field from the context that is threaded through every provider as `self`: - -```rust -#[cgp_fn] -pub fn greet(&self, #[implicit] name: &str) -> String { - format!("Hello, {name}!") -} -``` - -The `name` argument is supplied not by the caller but from the context, precisely as Koka's `ask()` is supplied by the enclosing `ask` handler rather than by `add-twice`'s caller. This is the same dynamic-binding pattern the [implicit-parameters](implicit-parameters.md) document describes, and the equivalence is not a loose analogy: the reader effect *is* the canonical tail-resumptive handler, and reading a context field *is* CGP's whole realization of it. Where the two part ways is *how the value is found* — Koka searches the dynamic handler stack, CGP reads a field of a statically-known context — which is the same resolution split that separates CGP from every dynamically-scoped mechanism. - -### Raising an error looks like the `raise` operation — but passes a value, not control - -CGP's error handling is the sharpest illustration of the boundary, because it mimics the *selection* of an exception handler while doing none of the *control transfer*. [`CanRaiseError`](../reference/components/can_raise_error.md) reads like the `raise` operation, and a provider raises without knowing the concrete error type: - -```rust -#[cgp_impl(new LoadOrFail)] -#[uses(CanRaiseError)] -#[use_type(HasErrorType.Error)] -impl Loader { - fn load(&self, path: &str) -> Result { - if path.is_empty() { - return Err(Self::raise_error("empty path".to_owned())); - } - Ok(format!("contents of {path}")) - } -} -``` - -Because the raise-and-wrap components carry per-source-type dispatch, a context can even route each source error to a different strategy, which reads like installing one exception handler per exception type — `RaiseFrom` for a `String`, `DebugError` for a `ParseError` — through the [`open` statement](../reference/macros/delegate_components.md) exactly as [modular error handling](../concepts/modular-error-handling.md) describes. But the resemblance stops at *selection*. An effect `raise` is the *zero-resume* handler: it abandons the continuation and unwinds to the handler. CGP's `raise_error` does not unwind anything — it *constructs and returns a value* of the abstract `Self::Error` type, and the actual abort is Rust's own `return`/`?` on the `Result`, entirely outside the wiring. CGP selects the *interpretation* of the error (the handler-choice half) but leaves the *control flow* (the continuation-discarding half) to `Result`. This is why CGP's dispatch is not even one-shot like OCaml's — it is strictly exactly-once, because the zero-resume case never lives inside the capability system at all. - -### Impl-side dependencies are the effect row; `check_components!` is "all effects handled" - -CGP tracks which capabilities a provider needs, and verifies they are all supplied, in a way that lines up closely with an effect system typing a row and demanding it be discharged. A provider's needs are stated as [impl-side dependencies](../concepts/impl-side-dependencies.md) — the `#[uses(CanRaiseError)]` above, and every bound in a `where` clause — which is the CGP counterpart of the effect row `` that Koka would infer for a function that performs `raise`. A generic provider over `Context` with such bounds is even effect-*polymorphic* in a loose sense: the context type variable plays a role like Koka's row variable, standing for "whatever else this context can do." And [`check_components!`](../reference/macros/check_components.md) is the discharge check: - -```rust -check_components! { - App { - LoaderComponent, - } -} -``` - -This asserts that `App` supplies every capability `LoadOrFail` transitively needs, and fails to compile naming the missing one if not — which is exactly Koka's guarantee that a program with an unhandled effect in its row is a type error, and the opposite of OCaml's runtime `Effect.Unhandled`. CGP lands on the statically-checked end of the effect-typing axis, reached through trait resolution rather than a dedicated effect system. - -### Configuring abstract types through the same wiring - -CGP extends the operations-and-handlers wiring to something effect systems do not touch: abstract *types*. Alongside choosing the provider for an operation, a context chooses the concrete type behind an [abstract type](../concepts/abstract-types.md) — its `Error`, its `Scalar`, its `Runtime` — through the same [`delegate_components!`](../reference/macros/delegate_components.md) table, by wiring a [`#[cgp_type]`](../reference/macros/cgp_type.md) component to [`UseType`](../reference/providers/use_type.md): - -```rust -delegate_components! { - App { - ErrorTypeProviderComponent: UseType, - } -} -``` - -An effect handler interprets operations, which are values and computations; it has no notion of supplying a *type member* the way `HasErrorType` supplies `Self::Error`. CGP unifies both under one wiring mechanism, so the same table that says "raise errors this way" also says "and the error type is `anyhow::Error`." This is a genuine capability beyond the effect-handler analogy, not a restatement of it. - -### What CGP cannot express - -The multi-shot power of effect handlers has no CGP analogue, and this is the honest limit of the comparison. Because a provider is an ordinary function that returns once, CGP cannot express any effect whose handler resumes the continuation zero times or more than once. Generators, backtracking search, cooperative scheduling, and async/await — the applications that motivate effect handlers in the first place ([Kammar, Lindley & Oury, *Handlers in Action*](https://denotational.co.uk/publications/kammar-lindley-oury-handlers-in-action.pdf)) — all require capturing the continuation and are simply outside CGP's model. CGP does have an [async handler family](../concepts/handlers.md) and [type-level DSLs](../concepts/type-level-dsls.md) that interpret a `Code` tag by dispatching to a provider, which is the closest CGP comes to the operations-and-interpreters shape; but even there the interpretation is a straight call chain resolved at compile time, not a captured continuation the handler controls. Async in CGP is Rust's own `async`/`await` threaded through provider calls, not a handler that suspends and resumes a computation. - -## What users like and dislike - -Algebraic effects are among the most admired ideas in current language research, and the reasons practitioners value them are consistent and concrete. The headline benefit is the elimination of *function coloring*: intermediate code between the operation and its handler needs no awareness of the effect, so effectful and pure code compose without the `async`/`sync` or `IO`-tainted split that plagues other approaches ([Abramov, *Algebraic Effects for the Rest of Us*](https://overreacted.io/algebraic-effects-for-the-rest-of-us/)). Users also prize the clean separation between an effect's *interface* — its operations — and its *semantics* — the handler — which makes the same code testable under a mock handler and runnable under a real one, and the *composability* of stacking several handlers to combine independent effects, widely contrasted favorably with monad transformers and their pain ([*Why Algebraic Effects?*, Ante](https://antelang.org/blog/why_effects/)). Where the effects are typed, as in Koka and Flix, the row or set in a signature is valued as honest documentation of what a function may do, and Flix turns that information into automatic parallelization and dead-code elimination. - -The complaints are equally consistent and fall into three clusters. The loudest is *unfamiliarity and control-flow opacity*: the concept is new to most programmers, no mainstream language ships it, and — the recurring technical objection — effects "abstract away side effects to effect handlers by definition," so that following control from a `perform` to the handler that catches it is as hard as reasoning about a distant exception handler, the "which handler runs this?" problem ([Ante](https://antelang.org/blog/why_effects/)). The second is *performance*: general handlers must capture continuations, which costs, and while optimizing compilers recover much of it for the tail-resumptive case through evidence passing, non-tail handlers remain more expensive than native control flow ([Xie & Leijen 2021](https://xnning.github.io/papers/multip.pdf)). The third is language-specific and cuts against the untyped designs: OCaml's decision to omit effect typing means a function's signature says nothing about the effects it performs and an unhandled effect is a runtime crash rather than a compile error ([OCaml manual](https://ocaml.org/manual/5.5/effects.html)), and its one-shot restriction rules out the multi-shot uses outright. For readers who reach effects from Haskell, the nearest comparison is the type-class-based effect libraries (`mtl` and its successors), whose recurring frustration is the *n² instances* problem — every handler must supply instances delegating all the *other* effects — which handler-based systems avoid ([`fused-effects`](https://hackage.haskell.org/package/fused-effects); [`effet`](https://hackage.haskell.org/package/effet)). - -## How CGP compares - -CGP and algebraic effects make opposite choices on the two axes that define the effect-handler design space — *what a handler may do* and *how it is chosen* — and the comparison is cleanest stated as that pair of trades. On *handler power*, an effect handler holds the continuation and may resume it any number of times, which buys generators, backtracking, and async; a CGP provider is a plain function that returns once, which buys nothing control-theoretic but costs nothing either — the call monomorphizes to a direct jump with no continuation to capture. On *handler selection*, an effect handler is chosen by *dynamic scope* — the nearest handler on the runtime stack wins, and a program can install a fresh handler for the same effect at any point — while a CGP provider is chosen by the context's *type* at compile time, fixed once in a wiring table and resolved through the trait system. That second axis places CGP much closer to type classes and implicit parameters than to effects, which is why the [dependency-injection](dependency-injection.md) and [implicit-parameters](implicit-parameters.md) comparisons cover ground this one deliberately leaves to them. The bridge between the two paradigms is evidence passing: Koka *compiles* dynamically-scoped handlers down to dictionary passing for the tail-resumptive case, and CGP is what you get if that compiled form is the only form there ever was — evidence passing chosen by types, with no dynamic search beneath it. - -Two further divergences follow from these and are worth stating plainly. First, an effect handler *stack* is ordered and nesting matters: the innermost handler for an operation wins, and reordering handlers changes results — state-over-nondeterminism and nondeterminism-over-state compute different things ([Kammar, Lindley & Oury 2013](https://denotational.co.uk/publications/kammar-lindley-oury-handlers-in-action.pdf)). CGP's wiring is a *flat* table: one provider per component, resolved by type, with no dynamic nesting to shadow an outer handler and no order to permute — and because the dispatch is exactly-once and resume-in-place, the non-commutativity that makes handler order significant simply does not arise. A CGP table is a set of handlers, not a stack of them. Second, CGP keeps even less of the "algebraic" than the practical effect languages do: it has no equational theory relating its operations, though since Koka, OCaml, and Flix mostly drop the laws too, this is a shared simplification rather than a CGP-specific gap. - -Neither design dominates, and the honest positioning names where each wins. When a program needs the continuation — a scheduler, a generator, a backtracking solver, a suspendable coroutine — algebraic effects are the right and only tool of the two, and emulating them with CGP is not possible, not merely awkward. When a program needs many interchangeable implementations of a capability chosen per deployment, checked statically, compiled to zero-overhead direct calls, and extended to abstract types as well as operations — and needs none of the continuation power — CGP delivers that on stable Rust, where an effect system would require a language the platform does not have. The exactly-once fragment CGP restricts itself to is not a crippled effect system; it is dynamic binding and dictionary passing, which is a complete and useful thing in its own right, and CGP adds to it the static per-context selection and abstract-type configuration that effect handlers do not offer. - -## Presenting CGP to someone who knows this - -A reader fluent in algebraic effects arrives with most of CGP's structure already in mind, and the fastest way in is to map the vocabulary and then immediately mark the one boundary. A **component is an effect signature**, its methods are **operations**, a **provider is a handler**, and **wiring is installing the handlers** on a context; a provider's `where` bounds are the **effect row** it performs, and **`check_components!` is the guarantee that every effect is handled**, the static version of Koka's unhandled-effect type error rather than OCaml's runtime crash. Reading a context field is **dynamic binding** — and here the correspondence is exact, not approximate, because dynamic binding *is* the canonical tail-resumptive handler and reading a field is CGP's whole realization of it. Framed this way, CGP is not a foreign paradigm to this reader but the *tail-resumptive corner* of the one they know, made static and type-directed. - -The boundary to draw at once, before it misleads, is the continuation. This reader will assume a CGP provider can resume, abort, or fork the computation the way a handler can, and it cannot: a provider is an ordinary function that returns exactly once, so there is no `resume` to call zero times or twice, and every multi-shot use they value — generators, async, backtracking — is outside CGP entirely. Present that not as a missing feature but as the deliberate location of CGP in the design space: it takes the fragment of effect handlers that the literature already identifies as dynamic binding, the fragment that compiles to direct calls with no continuation capture, and builds everything on it. The pitch that lands for this audience is "effect handlers minus the continuation, resolved by type instead of by dynamic scope, and extended to abstract types" — which reframes what could read as a limitation as a precise and defensible design choice. For the Koka or Flix reader specifically, lean on the shared row intuition: their effect row and CGP's impl-side dependencies are the same idea, and CGP's `check_components!` discharges it the same way their type checker does. For the OCaml reader, the resonant point is that CGP recovers the *static* discharge check their language chose to forgo, and does so without needing effect typing bolted onto the language. And for anyone who has fought monad transformers or `mtl`'s n² instances, the framing is that CGP composes capabilities without either — a flat table of per-context handlers, no stacking order to get wrong. - -The analogy to avoid is calling CGP "an effect system." It is not — it has no continuations, no dynamic scope, and no effect kind in the type system — and a reader sold on that framing will look for `resume` and feel misled when it is not there. Say precisely what CGP is: the exactly-once, resume-in-place fragment of effect handlers, which is dynamic binding, made into a compile-time, per-context, type-directed wiring mechanism that also configures abstract types. A reader who knows how much of effect-handler machinery exists to tame the continuation will recognize that a paradigm which never captures one has bought real simplicity, and will hear the trade as a considered one rather than a shortfall. - -## Sources - -The account of the related work draws on the primary research literature on algebraic effects and their compilation, the official documentation of Koka, OCaml, and Flix, and cited community writing for sentiment; the CGP snippets are drawn from the knowledge base's [consumer/provider](../concepts/consumer-and-provider-traits.md), [implicit-arguments](../concepts/implicit-arguments.md), and [modular error handling](../concepts/modular-error-handling.md) documents and verified against current macro behavior. - -- [Plotkin & Pretnar, *Handling Algebraic Effects* (LMCS 2013)](https://homepages.inf.ed.ac.uk/gdp/publications/handling-algebraic-effects.pdf) ([journal page](https://lmcs.episciences.org/705)) and [*Handlers of Algebraic Effects* (ESOP 2009)](https://homepages.inf.ed.ac.uk/gdp/publications/Effect_Handlers.pdf) — the foundational account of effects as operations with an equational theory and handlers as models interpreting them via the continuation. -- [Pretnar, *An Introduction to Algebraic Effects and Handlers*](https://www.eff-lang.org/handlers-tutorial.pdf) and [Bauer & Pretnar, *Programming with Algebraic Effects and Handlers*](https://www.researchgate.net/publication/221671686_Programming_with_Algebraic_Effects_and_Handlers) — an accessible tutorial and the Eff language, an untyped research realization. -- [Leijen, *Algebraic Effects for Functional Programming* (Koka)](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/08/algeff-tr-2016-v2.pdf) and [The Koka Programming Language book](https://koka-lang.github.io/koka/doc/book.html) — Koka's row-typed effects and the `ctl`/`fun`/`val` operation distinction. -- [Xie et al., *Effect Handlers, Evidently* (ICFP 2020)](https://www.microsoft.com/en-us/research/wp-content/uploads/2020/07/evidently-5f0b7dbc1a998.pdf) ([ACM](https://dl.acm.org/doi/abs/10.1145/3408981)) and [Xie & Leijen, *Generalized Evidence Passing for Effect Handlers* (ICFP 2021)](https://xnning.github.io/papers/multip.pdf) — the tail-resumptive-handler-is-dynamic-binding result and the evidence-passing (dictionary-passing) compilation of the exactly-once fragment. -- [OCaml manual, *Effect handlers*](https://ocaml.org/manual/5.5/effects.html) and [Sivaramakrishnan et al., *Retrofitting Effect Handlers onto OCaml* (PLDI 2021)](https://kcsrk.info/slides/handlers_edinburgh.pdf) — OCaml 5's `perform`/`continue` syntax, its one-shot continuations, its lack of effect typing, and the runtime `Effect.Unhandled`. -- [Flix Effect System documentation](https://doc.flix.dev/effect-system.html) and [Effects and Handlers](https://doc.flix.dev/effects-and-handlers.html), with [Madsen et al., *Programming with Purity Reflection* (ECOOP 2023)](https://drops.dagstuhl.de/entities/document/10.4230/LIPIcs.ECOOP.2023.18) — Flix's set-based effect system, its `eff`/`run … with handler` syntax, and purity reflection. -- [Kammar, Lindley & Oury, *Handlers in Action* (ICFP 2013)](https://denotational.co.uk/publications/kammar-lindley-oury-handlers-in-action.pdf) — handler composition, the significance of handler order, and the range of effects handlers express. -- [Abramov, *Algebraic Effects for the Rest of Us*](https://overreacted.io/algebraic-effects-for-the-rest-of-us/) and [*Why Algebraic Effects?* (Ante)](https://antelang.org/blog/why_effects/) — community accounts of what users value (no function coloring, composability, interface/semantics separation) and dislike (novelty, control-flow opacity, performance). -- [`fused-effects`](https://hackage.haskell.org/package/fused-effects) and [`effet`](https://hackage.haskell.org/package/effet) — the type-class-based effect libraries and the `mtl` n²-instances problem, for the Haskell reader's point of comparison. diff --git a/docs/related-work/dependency-injection.md b/docs/related-work/dependency-injection.md deleted file mode 100644 index 7da53d52..00000000 --- a/docs/related-work/dependency-injection.md +++ /dev/null @@ -1,194 +0,0 @@ -# Dependency injection - -Dependency injection (DI) is the practice of giving an object its collaborators from the outside instead of letting it construct them, and the frameworks built around it — Spring, Guice, Dagger, and their kin — automate the wiring so a large application's object graph assembles itself. CGP solves the same decoupling problem, but at compile time and without a container, so it is the concept a reader with an enterprise background is most likely to reach for when they first meet CGP wiring. - -## Purpose - -Every non-trivial program has to decide where its components get the things they depend on, and dependency injection is the answer that keeps those decisions out of the components themselves. A class that needs a database, an HTTP client, and a logger can create them in its constructor — hard-coding the concrete types — or it can accept them as parameters and let whoever builds it supply them. The second choice is dependency injection, and its payoff is decoupling: the class names only the *interfaces* it needs, so a test can pass fakes, a different deployment can pass different implementations, and the class never changes. The cost is that something has to do the supplying, and in a large graph that "something" is elaborate enough that frameworks exist to run it. - -This is exactly the problem CGP's wiring addresses, which is why the comparison matters. A DI framework assembles an object graph by matching each dependency to a provider; CGP assembles a context by matching each component to a provider. The vocabulary rhymes, the goal is identical — decouple what a piece of code *needs* from what supplies it — and the differences are all in the mechanism: a DI framework resolves the graph at runtime from reflection and configuration, while CGP resolves it at compile time through the trait system. A reader who understands why DI decouples code already understands why CGP does; the work is in showing them what changes when the resolution moves from runtime to types. - -## The concept in depth - -Dependency injection is a specific form of *inversion of control*: rather than a component reaching out to fetch its dependencies, control is inverted so the dependencies are handed to it. The idea predates any framework and is expressible in plain code — pass collaborators as constructor arguments — but the frameworks are what most practitioners mean by "DI," because they automate the assembly. The sections below cover the container-and-annotation model that Spring popularized, the module-and-binding model of Guice and Dagger, and the plain-code form that Rust already encourages. - -### The IoC container and beans (Spring) - -Spring's core is an *inversion-of-control container*: an object, the `ApplicationContext`, that instantiates, configures, and connects the application's objects — its *beans* — and manages their lifecycles. A class is marked as a bean with an annotation such as `@Component` or `@Service`, and the container discovers it by scanning the classpath. The container owns every bean it creates, so the application asks the container for a fully-assembled object rather than constructing one itself. - -```java -@Service -public class UserService { - // business logic lives here -} -``` - -The container is configured either by annotation-driven scanning, as above, or explicitly with a `@Configuration` class whose `@Bean` methods return the objects to manage. The explicit form is where a bean's construction is spelled out, including which implementation stands in for an interface: - -```java -@Configuration -public class AppConfig { - @Bean - public StorageClient storageClient() { - return new S3StorageClient(/* ... */); - } -} -``` - -### Constructor, setter, and field injection - -Once the container holds a set of beans, it injects each bean's dependencies by one of three mechanisms, and the choice among them is the most-discussed decision in day-to-day Spring. **Constructor injection** passes dependencies as constructor arguments, which makes them required and lets the field be `final`: - -```java -@Service -public class ProfilePictureService { - private final StorageClient storage; - private final UserRepository users; - - public ProfilePictureService(StorageClient storage, UserRepository users) { - this.storage = storage; - this.users = users; - } -} -``` - -**Setter injection** supplies a dependency through a setter after construction, which suits genuinely optional collaborators, and **field injection** writes the dependency straight into a private field by reflection, marked with `@Autowired`: - -```java -@Service -public class ProfilePictureService { - @Autowired private StorageClient storage; // field injection - @Autowired private UserRepository users; -} -``` - -The `@Autowired` annotation is the instruction to resolve a dependency by type: the container finds the one bean assignable to `StorageClient` and injects it. When a single constructor is present, Spring treats it as autowired without the annotation, which is why modern Spring code favors constructor injection and reserves `@Autowired` for the ambiguous or field-injected cases. The Spring team and the wider community now recommend constructor injection for all required dependencies, because it makes a class's dependencies explicit in its signature, keeps them non-null, and lets the object be built without a container in a test. - -### Modules and bindings (Guice and Dagger) - -Guice and Dagger express the same wiring not by classpath scanning but by explicit *bindings* declared in a *module*. A Guice module maps an interface to an implementation, and the injector resolves a request for the interface to the bound implementation: - -```java -public class StorageModule extends AbstractModule { - @Override - protected void configure() { - bind(StorageClient.class).to(S3StorageClient.class); - } -} -``` - -A class then requests its dependencies by annotating its constructor with `@Inject`, and the injector supplies them from the bindings. Dagger uses the same `@Inject`/module vocabulary but resolves the graph at *compile time*: its annotation processor generates the wiring code during the build, so a missing or ambiguous binding is a compile error and there is no reflection at runtime. This compile-time-versus-runtime split is the sharpest axis of variation among DI frameworks — Spring and Guice resolve bindings at runtime through reflection, Dagger resolves them at compile time through generated code — and it is the axis along which CGP sits firmly at the compile-time end. - -### Dependency injection without a framework (Rust) - -Rust practitioners generally hold that the language needs no DI framework, because traits and generics already provide the decoupling a container is built to deliver. A function that needs a capability takes a generic parameter bounded by a trait; a caller supplies any type implementing that trait; a test supplies a fake. Construction is separated from use by the ordinary discipline of taking collaborators as arguments rather than building them internally. - -```rust -trait StorageClient { - fn fetch(&self, object_id: &str) -> Vec; -} - -struct ProfilePictureService { - storage: S, -} -``` - -This is dependency injection in the original sense — dependencies supplied from outside, interfaces instead of concrete types — with the compiler doing the checking and no runtime container involved. Its limitation is the one CGP exists to lift: a generic bound like `S: StorageClient` leaks into every caller's signature, and coherence permits only one `impl StorageClient` per type, so swapping implementations per application, or offering several interchangeable ones, runs into the same walls that motivate CGP's [consumer/provider split](../concepts/coherence.md). CGP is best introduced to a Rust audience as the next step along this line they already accept, not as an import of the container model they have rejected. - -## How CGP expresses it - -CGP performs dependency injection through two mechanisms working together: a provider declares what it needs as [impl-side dependencies](../concepts/impl-side-dependencies.md), and a context supplies them by [wiring](../concepts/consumer-and-provider-traits.md) each component to a provider. The impl-side dependency is the counterpart of a constructor's parameter list, and the wiring table is the counterpart of the container's configuration — but both are resolved by the compiler, so the "container" has no runtime existence at all. - -### Impl-side dependencies are the injected constructor parameters - -A provider states the collaborators and values it needs in a way that reads like declaring dependencies, and CGP satisfies them from the context rather than from a container. Where a Spring service lists `StorageClient` and `UserRepository` as constructor parameters, a CGP provider lists its capability dependencies with [`#[uses(...)]`](../reference/attributes/uses.md) and its value dependencies with [`#[implicit]`](../reference/attributes/implicit.md) arguments. A user-creation provider that needs a database connection and a censorship service declares both: - -```rust -#[cgp_impl(new PostgresUserManager)] -#[uses(CanCensorUsername)] -#[use_type(HasErrorType.Error)] -impl UserManager { - fn create_user( - &self, - #[implicit] database: &PostgresDb, - username: &str, - email: &Email, - ) -> Result { - if self.username_is_censored(username) > Probability::new(0.8) { - return Err(Error::InvalidUsername); - } - // ... insert the user with `database` - } -} -``` - -The `#[uses(CanCensorUsername)]` line injects a *capability* — the same role a `UserRepository` collaborator plays in the Spring constructor — and the `#[implicit] database` argument injects a *value* pulled from the context's `database` field, the role a configuration bean plays. Neither dependency appears in the `CanManageUser` consumer trait a caller invokes, so, unlike a leaked generic bound, they do not cascade to callers — the decoupling a DI framework promises, delivered by hiding the requirements one level down in the provider's impl rather than in a container. - -### Wiring is the container configuration - -A context selects which provider satisfies each component in a [`delegate_components!`](../reference/macros/delegate_components.md) table, which is the direct analogue of a Spring `@Configuration` class or a Guice module: the one place where interfaces are mapped to implementations. Swapping an implementation is a one-line edit to this table, and — the crucial difference — two contexts can map the same component to different providers with no conflict, because the choice is keyed on the context type. A profile-picture service backed by different object stores in different deployments is two wiring tables: - -```rust -#[cgp_component(StorageObjectFetcher)] -pub trait CanFetchStorageObject { - fn fetch_storage_object(&self, object_id: &str) -> anyhow::Result>; -} - -delegate_components! { - App { - StorageObjectFetcherComponent: FetchS3Object, - } -} - -delegate_components! { - GCloudApp { - StorageObjectFetcherComponent: FetchGCloudObject, - } -} -``` - -`FetchS3Object` and `FetchGCloudObject` are interchangeable providers of the same capability — the CGP equivalent of two beans bound to one interface — and the wiring picks one per context. Because the selection is resolved during type-checking and monomorphized to a direct call, the `App` binary contains only the S3 code path and the `GCloudApp` binary only the GCloud one, with no runtime dispatch. A DI container makes the same substitution, but by holding both implementations and choosing at startup from configuration. - -### Checking replaces the container's startup validation - -A DI container discovers a missing or ambiguous binding when it assembles the graph — at startup for Spring and Guice, at build time for Dagger. CGP's counterpart is [`check_components!`](../reference/macros/check_components.md), which asserts at compile time that a context's wiring is complete and every provider's transitive dependencies are satisfied: - -```rust -check_components! { - App { - StorageObjectFetcherComponent, - } -} -``` - -If `FetchS3Object` needs a field or capability the `App` context does not supply, this fails to compile with the missing dependency named, rather than surfacing as a startup exception or a `NullPointerException` deep in a request. It is the same guarantee Dagger gives — the graph is verified before the program runs — reached through the trait system instead of an annotation processor. CGP wiring is [lazy](../concepts/check-traits.md), so this check is what turns a latent gap into an early, readable error. - -## What users like and dislike - -Dependency-injection frameworks are among the most widely adopted tools in enterprise software, and the reasons practitioners value them are real. They decouple components from their collaborators, which makes code testable — a fake is injected exactly where a real dependency would be — and swappable across environments. They centralize wiring, so the shape of an application's object graph lives in one readable place rather than scattered through constructors. And a mature framework like Spring brings an enormous ecosystem: transaction management, security, web bindings, and configuration all keyed off the same bean model, so adopting the container brings far more than injection. - -The complaints are equally well-documented, and they cluster around the runtime, reflective nature of the popular frameworks. The most common is that dependencies become *hidden*: with field injection, a class's signature says nothing about what it needs, so a reader must scan the whole class and a maintainer can add a dependency invisibly. Reflection-based resolution means a missing or mis-typed binding is a *runtime* failure — a startup exception or a `NullPointerException` in production — not a compile error, which is why the Spring community now steers hard toward constructor injection and away from field injection. There is a performance and startup cost to scanning the classpath and building the graph reflectively, which is why Dagger's compile-time generation exists and why it wins on Android and in latency-sensitive services. And there is the recurring complaint of *magic*: the framework does so much automatically, through reflection and proxies, that when something goes wrong the developer has little visibility into why, and the behavior is hard to reason about from the code alone. Even proponents concede the learning curve is steep and the machinery is heavy for a small program. - -## How CGP compares - -CGP takes the compile-time end of every axis the DI frameworks vary along, which is its central trade-off: it gives up runtime flexibility to gain static guarantees and zero cost. Because wiring is resolved by the trait system and monomorphized, there is no container, no reflection, no runtime graph, and no dynamic dispatch — a wired call compiles to a direct function call, and an unused provider is not in the binary. A dependency that a context fails to satisfy is a compile error at the wiring site, not a startup exception, so the class of failures that field injection is criticized for cannot occur. And a provider's dependencies are never hidden: they are stated in its `#[uses]` and `#[implicit]` declarations and enforced by the compiler, giving the explicitness the Spring community prizes in constructor injection, by default. - -The costs are just as real and should be stated plainly. CGP resolves everything at compile time, so it cannot do what a runtime container does best: reconfigure an application without recompiling, load plugins chosen at startup from a config file, or build a graph whose shape is not known until the program runs. It is confined to Rust, whereas Spring anchors a vast cross-cutting ecosystem that injection is only the entry point to. Its machinery — the consumer/provider split, [coherence-bypassing](../concepts/coherence.md) wiring, type-level tables — has a learning curve of its own, and its error messages, though they name the missing dependency under a check, can be verbose. A DI framework remains the better choice when the application genuinely needs runtime reconfiguration, when it lives in a JVM or .NET ecosystem whose libraries assume the container, or when the team's familiarity with the framework outweighs the benefits of static wiring. CGP wins where the graph is known at build time and the guarantees and zero cost matter: systems programming, latency-sensitive services, and libraries that must not impose a runtime. - -## Presenting CGP to someone who knows this - -A reader who knows dependency injection arrives with the right instinct — decouple what code needs from what supplies it — and the fastest way in is to map their vocabulary onto CGP's directly. A **provider** is a bean or a binding: an interchangeable implementation of a capability. **Wiring** with `delegate_components!` is the container configuration — the `@Configuration` class or the Guice module — the single place where interfaces are matched to implementations. An **impl-side dependency** is a constructor parameter: what a provider needs from the outside, declared where the implementation lives and never leaked to callers. And **`check_components!`** is the graph validation a container runs — the difference being *when* it runs. Leading with this dictionary lets the reader reuse everything they know about why DI decouples code, and spend their attention only on what is new. - -The one analogy to defuse immediately is the runtime container. A DI-trained reader will assume there is an object somewhere holding the graph, resolving dependencies by reflection, choosing implementations at startup — and there is not. CGP's "container" is the type system, the "graph" is a set of trait impls, and the resolution happens during compilation and compiles away to direct calls. Say this explicitly, because leaving it unsaid invites the reader to imagine a runtime cost and a runtime failure mode that do not exist. The framing that lands is *Dagger, taken further*: a reader who knows Dagger already accepts compile-time-verified injection with no reflection, and CGP is that same bargain with per-context choice added — the same interface can resolve to different implementations in different contexts, which a single global binding graph cannot express. - -The advantages worth foregrounding are the ones that answer this audience's own complaints. Dependencies are explicit and compiler-checked, so the hidden-dependency and `NullPointerException` failure modes that drove the community off field injection are gone by construction. There is no startup cost or reflection, so the performance objection that motivates Dagger is answered more completely. And nothing unused reaches the binary, which reads to this audience as automatic tree-shaking of the object graph. The expectation to address head-on is that CGP asks for explicit wiring and cannot scan-and-discover: there is no classpath scanning, no `@Component` auto-registration, no runtime rebinding, and the graph must be known at compile time. Present that not as a limitation grafted on but as the deliberate price of the guarantees — the same trade Dagger made, carried to its conclusion — and the reader who values static safety will read it as a feature rather than a loss. - -## Sources - -The account of the related work draws on the official framework documentation and representative community writing, cited where a specific claim rests on one. - -- [Spring Framework reference — Dependency Injection](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html) — the authoritative description of the IoC container, beans, and the constructor/setter injection mechanisms. -- [Baeldung — Inversion of Control and Dependency Injection in Spring](https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring) — the distinction between IoC and DI and the `@Autowired` autowiring-by-type behavior. -- [Comparing Dependency Injection Frameworks — Spring, Guice, Dagger, and Micronaut](https://medium.com/@AlexanderObregon/comparing-dependency-injection-frameworks-spring-guice-and-dagger-a614dccd5859) and [Dagger vs Guice](https://www.hackingnote.com/en/versus/dagger-vs-guice/) — the runtime-versus-compile-time split and the reflection-versus-generated-code trade-off across frameworks. -- [Field injection is not recommended (Marc Nuri)](https://blog.marcnuri.com/field-injection-is-not-recommended) and [James Shore — The Problem With Dependency Injection Frameworks](https://www.jamesshore.com/v2/blog/2023/the-problem-with-dependency-injection-frameworks) — the hidden-dependency, runtime-failure, and "magic" criticisms, and the case for constructor injection. -- [Rust traits and dependency injection (jmmv.dev)](https://jmmv.dev/2022/04/rust-traits-and-dependency-injection.html) — the position that Rust performs dependency injection through traits and generics without a framework. diff --git a/docs/related-work/dynamic-dispatch.md b/docs/related-work/dynamic-dispatch.md deleted file mode 100644 index c4210761..00000000 --- a/docs/related-work/dynamic-dispatch.md +++ /dev/null @@ -1,164 +0,0 @@ -# Dynamic dispatch, dynamic typing, and prototypal inheritance - -Dynamically-typed languages resolve almost everything at runtime: a method call is *dynamically dispatched* to an implementation chosen from the receiver, an object's behavior is looked up in a *vtable* or a method dictionary, code is written in *duck-typed* style that trusts a value to respond to the messages sent to it, and shared behavior is inherited by *delegation* along a prototype chain. CGP reproduces the openness all of this buys — many interchangeable implementations behind one interface, behavior assembled by delegation, defaults inherited and overridden, and provider code that reads as if it just sends messages to an object — but resolves every bit of it at compile time into direct, monomorphized calls, so its vtable is a type-level table erased before the program runs and its prototype chain is walked by the type checker rather than the CPU. - -## Purpose - -Dynamic dispatch exists to decouple a call site from the implementation it invokes, so that one piece of code works over many implementations chosen later. When code calls `shape.area()`, dynamic dispatch lets the concrete implementation be decided by the receiver rather than fixed where the call is written, which is what makes polymorphism, plugins, and open extension possible. Dynamically-typed languages take this to its limit: nothing about the receiver's type is fixed at compile time, so a value is whatever it can *do*, behavior is shared by pointing one object at another, and the whole program is malleable at runtime. The appeal is flexibility and immediacy — write against an interface without declaring it, extend without recompiling, explore in a REPL. - -This is the same decoupling CGP performs, which is why the comparison is illuminating rather than incidental, and the knowledge base already draws the connection: the [`delegate_components!`](../reference/macros/delegate_components.md) reference calls a context's wiring "a type-level table, analogous to an object's method table (vtable)," and the [bypassing coherence](../concepts/coherence.md) concept is careful to add that, unlike a real vtable, CGP's resolution is static and compiles down to direct calls with no runtime table and no dynamic dispatch. Both paradigms answer "how does a call reach an implementation chosen elsewhere?" — dynamic languages by looking it up at runtime, CGP by resolving it at compile time through the trait system. This document meets the reader who thinks in objects, messages, vtables, and prototypes, and shows where CGP's static mirror of those mechanisms lands. It is the object-oriented, dynamic-language counterpart to the type-theoretic [type classes](type-classes.md) and [ML modules](ml-modules.md) comparisons; the *type-system* side of duck typing — structural versus nominal typing — is developed in [row polymorphism](row-polymorphism.md), so this document leans on that one for the shape theory and keeps its own focus on dispatch, delegation, and the feel of the code. - -## The concept in depth - -Dynamic languages layer several ideas that a reader should keep distinct: *dynamic typing* and the *duck typing* style it enables, *dynamic dispatch* as the runtime resolution of a call, the *vtable* and method-dictionary machinery that implements it, and *prototypal inheritance* as the runtime sharing of behavior by delegation. The subsections build up in that order and close on the property of delegation — that `self` stays bound to the original object — that turns out to line up with CGP most precisely. - -### Dynamic typing and duck typing - -A dynamically-typed language checks types at runtime, and *duck typing* is the programming style this permits: an object's usability is decided by the methods and properties it actually has, not by a class it declares or an interface it implements. The maxim is "if it walks like a duck and quacks like a duck, then it is a duck" — a function that calls `x.quack()` works for *any* `x` that responds to `quack`, with no declared relationship between them ([*Duck typing*, Wikipedia](https://en.wikipedia.org/wiki/Duck_typing)). Code written this way sends messages to a value and trusts it to respond, deferring the question "does it actually have this method?" to the moment the call runs. Python, Ruby, JavaScript, and Smalltalk are the canonical homes of the style, and its whole appeal is that an interface need never be spelled out: you write the calls, and any object that can service them qualifies. - -### Dynamic dispatch and late binding - -Dynamic dispatch is the runtime selection of which implementation a method call invokes, based on the receiver. Also called *late binding*, it is the mechanism where the association between a call and the code it runs is resolved at runtime rather than at compile time, and it is what makes a single interface invoke different underlying methods depending on the object's actual type ([*Dynamic dispatch*, Wikipedia](https://en.wikipedia.org/wiki/Dynamic_dispatch)). Smalltalk gave the purest form: every call is a *message send*, resolved by a `send` operation that takes the receiver and the message name and, *at call time*, consults the receiver's class method dictionary to find the method to run ([*Dynamic dispatch*, Wikipedia](https://en.wikipedia.org/wiki/Dynamic_dispatch)). Statically-typed object languages — C++, Java, Rust — offer the same late binding for methods marked virtual, dispatching on the single receiver; a few languages (CLOS, Julia) generalize to *multiple dispatch*, choosing on the runtime types of several arguments at once. - -### Vtables and method dictionaries - -Dynamic dispatch is implemented, in compiled languages, by a *virtual method table*: a per-class array of function pointers, one slot per virtual method, that each object reaches through a hidden vtable pointer. A virtual call loads the vtable pointer from the object, indexes to the method's slot, and calls the function pointer found there — an indirection resolved entirely at runtime ([*Virtual method table*, Wikipedia](https://en.wikipedia.org/wiki/Virtual_method_table)). Rust's own dynamic dispatch works exactly this way: a `dyn Trait` value is a *fat pointer* pairing a data pointer with a vtable pointer, and the vtable is a statically-built table holding the type's destructor, size, and alignment followed by its method pointers, so a call through `dyn Trait` loads the vtable, looks up the method, and calls it with the data pointer ([geo-ant, *Rust Dyn Trait Objects and Fat Pointers*](https://geo-ant.github.io/blog/2023/rust-dyn-trait-objects-fat-pointers/); [*Trait objects*, The Rust Programming Language Book](https://doc.rust-lang.org/book/ch18-02-trait-objects.html)). The cost is real: an indirect call defeats inlining and adds a load per dispatch. Dynamically-typed languages pay even more, resolving a method by name through a dictionary up the class or prototype chain — which is why their implementations invest heavily in *inline caches* and *hidden classes*, techniques pioneered in the Self language's *maps* and *polymorphic inline caches* and inherited by V8 to make repeated same-shape access fast ([*Maps (Hidden Classes) in V8*](https://v8.dev/docs/hidden-classes)). The vtable is the enduring image: a table of function pointers, consulted at runtime, that maps an interface's methods to a type's implementations. - -### Prototypal inheritance and delegation - -Prototype-based languages share behavior not through classes but by *delegation*: an object holds a link to another object, its prototype, and a message the object does not handle is forwarded to the prototype, walking a chain until the message is answered or the chain ends. This model, introduced by Henry Lieberman's 1986 *Using Prototypical Objects to Implement Shared Behavior in Object-Oriented Systems* and realized in the Self language, needs no classes: an object is a blueprint for others, and behavior is inherited by pointing at it ([Lieberman, *Using Prototypical Objects…*](https://web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html)). JavaScript is its most widely-used descendant — every object has a `[[Prototype]]` link, `Object.create(proto)` sets it, and a property lookup walks the prototype chain, with an own property *shadowing* an inherited one ([MDN, *Inheritance and the prototype chain*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain)). Lua expresses the same idea through the `__index` metamethod, and a missing method can even be caught by a fallback hook — Ruby's `method_missing`, Python's `__getattr__`, Lua's `__index` function. - -The property that distinguishes *delegation* from mere forwarding is the one that matters most for CGP: `self` stays bound to the original receiver. When an object delegates a message to its prototype and the prototype's method refers to `self`, that `self` denotes the object that *originally received* the message, not the prototype the method was found on ([Lieberman, *Using Prototypical Objects…*](https://web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html)). This is what makes delegation a form of inheritance rather than plain message forwarding: the delegate supplies the *behavior*, but the original object remains the *identity* that behavior runs against, so a method inherited from a prototype still sees the receiver's own state. Forwarding, by contrast, rebinds `self` to the object the message was passed to, losing that connection. The delegation-keeps-self-bound rule is the precise hinge on which the CGP comparison turns. - -## How CGP expresses it - -CGP reproduces dynamic dispatch, vtables, and prototypal delegation, but resolves each at compile time, so what is a runtime lookup in a dynamic language is a type-resolution step in CGP that monomorphizes to a direct call. A [component](../reference/macros/cgp_component.md) is the interface, a [provider](../reference/macros/cgp_impl.md) is an implementation, the context's [wiring table](../reference/macros/delegate_components.md) is its vtable, and [aggregate providers](../concepts/aggregate-providers.md) and [namespaces](../concepts/namespaces.md) are its prototype chain. The correspondence is close construct by construct, and it breaks in exactly one place — everything is static — which is the source of both what CGP gains and what it gives up. - -### CGP code reads like a duck-typed program - -The most immediate resemblance is stylistic: a CGP provider is written against an unknown context and reads like duck-typed code that sends messages to a receiver and trusts it to respond. A provider calls methods on `self` and reads values from it without ever naming a concrete type or declaring the interface it depends on: - -```rust -#[cgp_impl(new GreetHello)] -#[uses(HasName)] -impl Greeter { - fn greet(&self) { - println!("Hello, {}!", self.name()); - } -} -``` - -The body `self.name()` is a message send to a context whose type is not written down, and with an [`#[implicit]`](../reference/attributes/implicit.md) argument the dynamic feel is stronger still — a value simply materializes from the context, as an unbound name would in Ruby or Python: - -```rust -#[cgp_fn] -pub fn greet(&self, #[implicit] name: &str) -> String { - format!("Hello, {name}!") -} -``` - -This is nothing like ordinary statically-typed Rust, where calling `x.name()` demands a concrete `x: Person` or a spelled-out bound `fn greet(c: &C)` that names the capability in the signature. CGP hides the bound behind [`#[uses]`](../reference/attributes/uses.md) and abstracts the context away, so the provider body carries *less* visible type ceremony than a plain generic function and reads as if it were duck-typed — "assume the context has a `name`, and greeting works." The decisive difference is *when* the trust is discharged. A duck-typed program finds out at runtime whether the object responds, failing with an `AttributeError` or `NoMethodError` if not; CGP finds out at compile time, because the `#[uses(HasName)]` dependency and the field access are checked by trait resolution and by [`check_components!`](../reference/macros/check_components.md) — which is Ruby's `respond_to?` guard moved to compile time and made total. And the field-based half of this is genuinely structural, not merely stylistic: an `#[implicit]` argument or a [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) resolves against *any* context carrying a matching field, the duck-typing-as-a-type-system idea that [row polymorphism](row-polymorphism.md) develops in full. CGP code looks duck-typed on the surface while its resolution stays static and, at the wiring, nominal. - -### Static dispatch with the flexibility of dynamic dispatch - -CGP delivers the openness of dynamic dispatch — one interface, many implementations, the choice made late — but resolves the choice at compile time and compiles it to a direct call. A caller writes `context.area()` against the `CanCalculateArea` consumer trait without naming an implementation, exactly as a dynamic call names a method and lets the receiver decide; the difference is that CGP's "receiver decides" happens during type checking. The consumer-trait blanket impl generated by [`#[cgp_component]`](../reference/macros/cgp_component.md) routes the call through the context's [`DelegateComponent`](../reference/traits/delegate_component.md) table to the wired provider, and the whole routing is resolved by the compiler and [monomorphized to a direct, zero-cost call](../concepts/coherence.md) — no fat pointer, no vtable load, no indirect jump. Rust already offers *real* dynamic dispatch through `dyn Trait`, and CGP is deliberately its static sibling: both let an implementation be chosen after the calling code is written, one paying a runtime vtable indirection to do it and the other resolving it away entirely. Where a component carries a generic parameter, CGP even reproduces *multiple* dispatch — the [`open` statement](../reference/macros/delegate_components.md) selects a provider per value of a type argument, dispatching on the context *and* that argument the way multimethods dispatch on several runtime types, but decided at compile time. - -### `DelegateComponent` is a compile-time vtable - -The type-level table a context carries is a vtable that exists only during compilation. Each [`DelegateComponent`](../reference/traits/delegate_component.md) impl on a context maps one component key to the provider that implements it, precisely as a vtable slot maps a method to its implementation: - -```rust -delegate_components! { - Rectangle { - AreaCalculatorComponent: RectangleArea, - } -} - -// expands to the type-level table entry: -// impl DelegateComponent for Rectangle { -// type Delegate = RectangleArea; -// } -``` - -The mapping to a vtable is exact on structure and opposite on timing. A context type plays the role of a class, a `DelegateComponent` impl is a vtable slot, and the provider it names is the function that slot points to — but the key is a *type* (the component marker) rather than a method offset, the lookup is performed *once by the compiler* rather than on every call by the CPU, and the table is *erased* before the program runs rather than materialized as data an object points to. This is why the [`delegate_components!`](../reference/macros/delegate_components.md) reference introduces the table as "analogous to a vtable… resolved at compile time" — the analogy is precise, and the qualification is the whole point. The honest cost of resolving the vtable away is that CGP loses the runtime heterogeneity a real vtable enables: a `Vec>` can hold different concrete shapes and dispatch each at runtime, whereas a CGP context is one monomorphic type resolved once, so mixing implementations in a single collection and choosing between them at runtime is the job of Rust's `dyn`, not of CGP. - -### Component delegation is delegation, with `self` bound - -CGP's delegation chain is delegation in Lieberman's exact sense, because the context stays bound to the original as lookup walks the chain. An [aggregate provider](../concepts/aggregate-providers.md) bundles a group of component wirings, and a context delegates a whole group to it in one entry, so resolution walks from the context through the bundle to a leaf provider: - -```rust -delegate_components! { - new GeometryComponents { - AreaCalculatorComponent: RectangleArea, - PerimeterCalculatorComponent: RectanglePerimeter, - } -} - -delegate_components! { - Rectangle { - [AreaCalculatorComponent, PerimeterCalculatorComponent]: GeometryComponents, - } -} -``` - -When `rect.area()` resolves, the lookup walks `Rectangle` → `GeometryComponents` → `RectangleArea`, and — this is the crux — the *context stays `Rectangle`* at every step: the [aggregate-providers concept](../concepts/aggregate-providers.md) states outright that "the context argument is `Rectangle` at every step; `GeometryComponents` appears only in the `Self`/delegate position, never as the `Context`," so the leaf provider reads its fields and capabilities from `Rectangle`, the real context, not from the bundle. That is delegation's defining property precisely: the delegate chain supplies the *behavior* while `self` — the context — remains the original *identity* that behavior runs against, never rebound to the prototype the method was found on. The [`UseContext`](../reference/providers/use_context.md) provider is the same relationship pointed the other way, letting a provider route a call back to the context's own wiring — the prototype consulting its delegator. CGP's delegation is not analogous to prototypal delegation; on the axis that separates delegation from forwarding, it *is* delegation, resolved statically. - -### Namespaces are prototypal inheritance with override - -A CGP namespace is a prototype: a reusable object of default wirings that a context inherits and then selectively shadows. A context joins a namespace and inherits every entry it does not wire directly, and a directly-wired entry on the context *wins* over the inherited one — own-property-shadows-prototype, expressed at the type level ([namespaces concept](../concepts/namespaces.md)): - -```rust -delegate_components! { - App { - namespace DefaultNamespace; // inherit the namespace's wirings as defaults - - // a directly-wired entry here shadows the namespace's entry for that key - } -} -``` - -Namespaces inherit from one another exactly as prototypes chain into further prototypes, so a base namespace can be extended into a richer one that contexts downstream pick up: - -```rust -cgp_namespace! { - new ExtendedNamespace: DefaultNamespace { - @cgp.core.error => @app, - } -} -``` - -`ExtendedNamespace` resolves everything `DefaultNamespace` does plus its own entries, the prototype-of-a-prototype relationship at the wiring level. The lookup that walks these layers is the [`RedirectLookup`](../reference/providers/redirect_lookup.md) provider tracing a type-level path, which is the prototype chain being walked — and the redirect that fires when a key is not directly present is the compile-time analogue of a `__index` or `method_missing` fallback. As with everything else in CGP, the chain is walked by trait resolution during compilation rather than by property lookup at runtime, so the inheritance is real but pays nothing at runtime and cannot be mutated once the program is built. - -## What users like and dislike - -Dynamic dispatch and dynamic typing are loved for the immediacy and flexibility they give, and the praise is consistent across their communities. Duck typing lets a function work over any object that responds to the messages it sends, which programmers value for *flexibility and reuse* — no interface to declare, no hierarchy to fit into — and for *cleaner, shorter code* and *rapid prototyping*, the reasons Ruby and Python developers reach for it ([SitePoint, *Making Ruby Quack*](https://www.sitepoint.com/making-ruby-quack-why-we-love-duck-typing/); [GeeksforGeeks, *Type Systems*](https://www.geeksforgeeks.org/python/type-systemsdynamic-typing-static-typing-duck-typing/)). Dynamic dispatch is what makes polymorphism and open extension work, and prototypal inheritance is prized for its malleability — objects can be created, linked, and modified at runtime, behavior can be shared without a class hierarchy, and a running program can be reshaped live. Metaprogramming hooks like `method_missing` and `__getattr__` let a single object answer messages it was never written to handle, which powers proxies, DSLs, and ORMs. - -The complaints are the mirror image of that flexibility, and they cluster on safety and cost. Because type checks are deferred, a mistake surfaces as a *runtime error* — an `AttributeError`, a `NoMethodError`, an "undefined is not a function" — discovered only when the offending line runs, which makes refactoring hazardous and large systems harder to trust; the standard mitigation is to add tests, type annotations, or a `respond_to?` guard before the call ([DevGex, *Duck Typing*](https://devgex.com/en/article/00035033); [SitePoint](https://www.sitepoint.com/making-ruby-quack-why-we-love-duck-typing/)). Dynamic dispatch has a *performance cost* — the vtable indirection defeats inlining, and dictionary-based lookup in dynamic languages is worse, which is why so much engineering goes into inline caches and hidden classes to claw it back ([V8, *Hidden Classes*](https://v8.dev/docs/hidden-classes)). Prototypal inheritance draws its own specific gripes: a mutable prototype chain is easy to get confused about, and JavaScript's `this` binding — the very self-binding that makes delegation work — is a notorious source of bugs when a method is detached from its receiver. And tooling suffers throughout, since an IDE cannot reliably know what a value responds to when its type is not fixed. - -## How CGP compares - -CGP takes the static end of every axis these mechanisms define, which is its central trade: it keeps the openness and gives up the runtime. On *dispatch*, CGP resolves at compile time and monomorphizes where a dynamic language resolves at runtime, so there is no vtable, no method-dictionary lookup, and no indirect call — a wired call is a direct one, and nothing about it survives into the running program. On *typing*, CGP writes duck-typed-looking provider code but checks it statically, so the missing-method failure that a dynamic language hits at runtime is a compile error at the wiring site, caught by `check_components!` rather than by a production stack trace. On *inheritance*, CGP's delegation and namespace chains are walked by the type checker, not by runtime property lookup, so they cost nothing and cannot go wrong through a mis-set prototype or a detached `this`. Each side pays for what the other gets. Dynamic languages get runtime malleability — heterogeneous collections, plugins loaded at startup, live redefinition, metaprogramming — and pay with runtime errors and dispatch overhead; CGP gets zero-cost dispatch and static guarantees and pays by fixing the wiring at compile time, so nothing about it can change while the program runs. - -The costs on CGP's side are worth naming plainly, because they are exactly the capabilities dynamic dispatch exists to provide. CGP cannot hold a heterogeneous collection of implementations and choose among them at runtime — that is what Rust's own `dyn Trait` is for, and a program that needs it should reach for a trait object, not for CGP. It cannot load an implementation chosen at runtime from configuration or a plugin file, monkey-patch a live object, or synthesize a response to a message it was not built to handle, because there is no runtime object graph to mutate and no runtime dispatch to intercept. And its errors, though caught early, are the verbose generated-type messages the [check traits](../concepts/check-traits.md) exist to localize, rather than the direct `NoMethodError` a dynamic language reports. - -Neither is uniformly better, and the honest positioning names where each wins. When a program needs runtime openness — plugins discovered at startup, objects of mixed types in one collection, behavior reshaped live, or the metaprogramming that proxies and DSLs rely on — dynamic dispatch is the right tool, and emulating it with CGP's compile-time wiring is not merely awkward but impossible, since the whole point is deferral to runtime. When a program's set of implementations is known at build time and it wants the decoupling of dynamic dispatch with none of the cost or the runtime failure modes — polymorphism that inlines, duck-typed ergonomics that cannot throw `NoMethodError`, and inheritance that the compiler resolves — CGP delivers that on stable Rust, and does so as ordinary types with no runtime machinery at all. - -## Presenting CGP to someone who knows this - -A reader who thinks in objects, messages, and prototypes holds most of CGP's structure already, and the way in is to map the vocabulary and then flag the one change. A **context is an object**, a **component is a message or method**, a **provider is a method implementation**, **wiring is the object's method table**, [`DelegateComponent`](../reference/traits/delegate_component.md) **is its vtable**, an **aggregate provider or namespace is a prototype** the object delegates to, and [`check_components!`](../reference/macros/check_components.md) **is the guarantee that the object responds to every message it will be sent** — `respond_to?` made total and moved to compile time. Reading a provider body, which sends messages to a context it never names, is reading duck-typed code. Framed this way, CGP is not a foreign paradigm to this reader but the mechanisms they already use — dispatch, vtables, delegation, duck typing — with the runtime taken out. - -The single expectation to correct, from which everything else follows, is that any of this happens at runtime. This reader will assume a vtable is a data structure the program carries, that dispatch chooses at the moment of the call, that a prototype chain is walked when a property is missing, and that the object graph can change while the program runs — and in CGP none of that is so. The table is erased after compilation, the dispatch is resolved by the type checker and inlined, the chain is walked during type resolution, and the wiring is fixed once the program is built. Present "late binding" honestly: in CGP the binding is late to the *wiring site* — the place a context declares its providers — not to runtime, so the flexibility is real but it is spent at compile time. The pitch that lands for this audience is the pair of things they most wish their own tools had: *duck typing that cannot blow up at runtime*, because a context missing a capability is a compile error rather than a 2 a.m. `NoMethodError`, and *dynamic dispatch that costs nothing*, because the polymorphism they rely on is resolved away instead of paid for on every call. For the prototype-minded reader specifically, the resonant framing is that CGP's delegation keeps `self` bound to the original context exactly as theirs keeps `this` bound to the receiver — same inheritance-by-delegation, but checked and free. - -The analogy to avoid is promising runtime behavior CGP does not have. A reader sold on heterogeneous collections, runtime plugins, or monkey-patching will feel misled the first time they reach for them and find the wiring frozen at compile time; say plainly that those live on the runtime side of the line, where Rust's `dyn Trait` and the dynamic languages themselves remain the right tools. Sell CGP as what it is — the static resolution of the mechanisms they know, trading runtime malleability for zero cost and compile-time safety — and the reader who has debugged a `this`-binding bug or chased a `NoMethodError` through a plugin will hear the trade as a good one. - -## Sources - -The account of the related work draws on standard references for dynamic dispatch and object models, the primary literature on prototype-based programming, and cited community writing for sentiment; the CGP snippets are drawn from the knowledge base's [aggregate providers](../concepts/aggregate-providers.md), [namespaces](../concepts/namespaces.md), and [consumer/provider](../concepts/consumer-and-provider-traits.md) material and verified against current macro behavior. - -- [*Dynamic dispatch* (Wikipedia)](https://en.wikipedia.org/wiki/Dynamic_dispatch) and [*Virtual method table* (Wikipedia)](https://en.wikipedia.org/wiki/Virtual_method_table) — late binding as runtime resolution of a call, Smalltalk's message-send `send`, single versus multiple dispatch, and the per-class vtable of function pointers reached through an object's vtable pointer. -- [*Duck typing* (Wikipedia)](https://en.wikipedia.org/wiki/Duck_typing) — an object's usability decided by the methods and properties it has rather than a declared class or interface. -- [geo-ant, *Rust Dyn Trait Objects and Fat Pointers*](https://geo-ant.github.io/blog/2023/rust-dyn-trait-objects-fat-pointers/) and [*Trait objects*, The Rust Programming Language Book](https://doc.rust-lang.org/book/ch18-02-trait-objects.html) — Rust's own dynamic dispatch as a data-pointer/vtable-pointer fat pointer, and the vtable's contents, the runtime counterpart to CGP's compile-time table. -- [Lieberman, *Using Prototypical Objects to Implement Shared Behavior in Object-Oriented Systems* (OOPSLA 1986)](https://web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html) — delegation as forwarding unhandled messages to a prototype, and the defining rule that `self` stays bound to the original receiver, which distinguishes delegation from forwarding and matches CGP's context binding. -- [MDN, *Inheritance and the prototype chain*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain) — JavaScript's `[[Prototype]]` link, `Object.create`, prototype-chain lookup, and own-property shadowing, the runtime form of namespace inherit-and-override. -- [*Maps (Hidden Classes) in V8*](https://v8.dev/docs/hidden-classes) — hidden classes and inline caches descending from the Self language's maps and polymorphic inline caches, the engineering that dynamic dispatch's runtime cost demands. -- [SitePoint, *Making Ruby Quack — Why We Love Duck Typing*](https://www.sitepoint.com/making-ruby-quack-why-we-love-duck-typing/), [GeeksforGeeks, *Type Systems: Dynamic, Static & Duck Typing*](https://www.geeksforgeeks.org/python/type-systemsdynamic-typing-static-typing-duck-typing/), and [DevGex, *Duck Typing*](https://devgex.com/en/article/00035033) — community sentiment on what duck typing and dynamic typing buy (flexibility, brevity, rapid development) and cost (runtime errors, the `respond_to?` guard, refactoring hazards). diff --git a/docs/related-work/implicit-parameters.md b/docs/related-work/implicit-parameters.md deleted file mode 100644 index 2332afaf..00000000 --- a/docs/related-work/implicit-parameters.md +++ /dev/null @@ -1,165 +0,0 @@ -# Implicit parameters - -Implicit parameters are function arguments the compiler supplies automatically from the surrounding context instead of the caller passing them by hand — Scala's `given`/`using` and Haskell's `ImplicitParams` are the direct forms, and the type-class resolution both languages build on is the same idea generalized. CGP shares the goal of threading context through code without explicit plumbing, but supplies the values from a context's fields and wiring rather than from a compiler-driven search, which trades away automatic resolution to gain the freedom to have many overlapping implementations. - -## Purpose - -Some values are needed everywhere and interesting nowhere: a configuration object, a logger, a comparison strategy, an error type, an execution environment. Passing them explicitly through every function that transitively needs them clutters signatures and forces intermediate functions to accept parameters they only forward. Implicit parameters remove that clutter by letting the caller omit such an argument and having the compiler fill it in from what is in scope, so the value propagates through a call chain without appearing at every call. The declaration still records the dependency in the type, but the *passing* becomes invisible. - -This is the same tension CGP resolves with [impl-side dependencies](../concepts/impl-side-dependencies.md) and [implicit arguments](../concepts/implicit-arguments.md), which is why the comparison is illuminating rather than incidental. Both CGP and the implicit-parameter languages want a piece of code deep in a call graph to obtain what it needs from its surroundings without every caller in between having to know. They differ in *what the surroundings are* and *how the value is found*: Scala and Haskell search an implicit scope by type, while CGP reads a field or resolves a wiring entry from the context that is already threaded through every provider. Showing a reader that CGP's context *is* their implicit environment — made a first-class, explicitly-wired type — is the heart of the comparison. - -## The concept in depth - -Implicit parameters appear in several forms that a reader may or may not distinguish, and the comparison to CGP is clearest when they are kept apart. There is the direct implicit *value* parameter (Scala's `using`, Haskell's `ImplicitParams`), and there is the type-class mechanism (Scala's `given` type classes, Haskell's `class`/`instance`) that is implicit resolution specialized to finding an implementation for a type. Both rest on the compiler searching a scope by type, and both are governed by *coherence*, the property that makes CGP's approach fundamentally different. - -### Context parameters in Scala (`using` and `given`) - -Scala 3 lets a parameter list be marked `using`, which makes its arguments contextual: at the call site the programmer may omit them, and the compiler supplies a matching `given` value from scope. A function that needs a `Config` everywhere in a rendering pipeline declares it once with `using` and never threads it again: - -```scala -def renderWebsite(path: String)(using config: Config): String = - "" + renderWidget(List("cart")) + "" // config passed implicitly - -def renderWidget(items: List[String])(using config: Config): String = ??? -``` - -A `given` value in scope is what the compiler injects for a `using` parameter, and the caller writes nothing: - -```scala -given Config = Config(8080, "docs.scala-lang.org") - -renderWebsite("/home") // the given Config is supplied automatically -``` - -The mechanism generalizes to type classes, which is its most common use. A `given` can be defined *for a type*, and a method that takes a `using` parameter of that type resolves the right instance by the types at the call site: - -```scala -trait Comparator[A]: - def compare(x: A, y: A): Int - -given Comparator[Int] with - def compare(x: Int, y: Int): Int = x - y - -def max[A](x: A, y: A)(using c: Comparator[A]): A = - if c.compare(x, y) > 0 then x else y - -max(1, 2) // Comparator[Int] resolved and passed implicitly -``` - -Scala uses this one mechanism for both dependency injection and type classes — a `using Config` is DI, a `using Comparator[A]` is a type class — which is why the language's implicits sit at the intersection of the two related-work topics; see also [dependency injection](dependency-injection.md). Historically this was all written with the `implicit` keyword in Scala 2; Scala 3 renamed it to `given`/`using` precisely because the old keyword was overloaded and hard to teach. - -### Implicit parameters in Haskell (`ImplicitParams`) - -Haskell's `ImplicitParams` extension is the more literal form of the same idea: a function can name a dynamically-bound variable `?x` of some type, which shows up as a constraint `(?x :: t)` on its signature and is filled from the binding in scope. A sort that needs a comparison declares it as an implicit parameter and calls it as `?cmp`: - -```haskell -sort :: (?cmp :: a -> a -> Bool) => [a] -> [a] -sort = sortBy ?cmp -``` - -The constraint propagates automatically to any caller that does not bind it, so a function built on `sort` inherits its `?cmp` without restating the intent: - -```haskell -least :: (?cmp :: a -> a -> Bool) => [a] -> a -least xs = head (sort xs) -``` - -The value is supplied with a `let` binding, at which point the constraint is discharged and the function below it becomes ordinary: - -```haskell -min :: Ord a => [a] -> a -min = let ?cmp = (<=) in least -``` - -`ImplicitParams` is the honest baseline for the comparison but a cautionary one: it is barely used in modern Haskell. The constraints leak into every signature, so the parameters are not very "implicit"; there is no way to declare a default; and the feature interacts awkwardly with the monomorphism restriction. Haskell programmers reach instead for the `Reader` monad to thread an environment, or, far more often, for type classes. - -### Type classes as implicit dictionary passing - -Type classes are the mechanism Haskell and Scala actually use for the implicit-resolution job, and understanding them as *implicit dictionary passing* is what connects them to CGP. A `class` declares an interface, an `instance` provides it for a type, and when a function constrained by the class is called, the compiler finds the instance for the concrete type and passes it as a hidden argument — a *dictionary* of the instance's methods. The constraint `Ord a =>` is elaborated into an extra parameter carrying the `Ord` dictionary: - -```haskell -class Ord a where - compare :: a -> a -> Ordering - -instance Ord Int where - compare x y = ... -- the compiler builds and passes an Ord Int dictionary -``` - -The resolution is *type-directed and automatic*: the programmer names the constraint, and the compiler searches for the matching instance by type, with no explicit selection. This is exactly the convenience CGP gives up and the reason it can do something type classes cannot, so the next point is the pivot of the whole comparison. - -### Coherence: one instance, globally - -The property that governs type-class and implicit resolution — and that CGP deliberately abandons — is *coherence*: for a given type there is exactly one instance, and every resolution anywhere in the program finds the same one. Coherence is what makes automatic resolution safe. Because the compiler will always find the same `Ord Int`, it can inject it silently without the programmer worrying that a different `Ord Int` might be chosen elsewhere and make two pieces of code disagree. Haskell enforces this with the orphan-instance rule and by forbidding overlapping instances; Scala's implicits are coherent in the common case and rely on scoping and priority rules at the edges. - -The price of coherence is the price CGP was built to escape. Because there can be only one `Ord Int`, a program cannot have two legitimate orderings of `Int` as first-class instances — the standard workaround is to wrap the type in a `newtype` so a second instance attaches to a distinct type. And a crate cannot add an instance for a type and class it does not own. These are the same overlap and orphan restrictions Rust's own coherence imposes, described from the CGP side in [bypassing coherence](../concepts/coherence.md). The comparison to CGP therefore comes down to a single trade: implicit resolution buys automatic, type-directed selection at the cost of one global instance per type, while CGP buys many overlapping per-context implementations at the cost of selecting them explicitly. - -## How CGP expresses it - -CGP threads context through code and lets deep code read what it needs, but it does so through the context that is already the `Self` of every provider, not through a scope search. The value a Scala `using` parameter or a Haskell implicit parameter carries is, in CGP, a field of the context or an entry in its wiring — and the context is passed to every provider automatically as the receiver, so nothing has to be threaded by hand. Two CGP constructs map onto the two forms of implicit parameter: [`#[implicit]`](../reference/attributes/implicit.md) arguments correspond to implicit *value* parameters, and the [consumer/provider component](../concepts/consumer-and-provider-traits.md) with its wiring corresponds to type classes. - -### Implicit arguments are implicit value parameters - -An `#[implicit]` argument is written as an ordinary function parameter but is supplied from the context's fields rather than by the caller, which is precisely the shape of a Scala `using` parameter — except the source is a struct field, not a `given` in scope. A provider that needs width and height reads them as implicit arguments, and any context carrying those fields supplies them: - -```rust -#[cgp_fn] -pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} -``` - -Where Scala's `renderWebsite` obtains its `Config` from an implicit `given`, a CGP provider obtains `width` and `height` from the context threaded through it as `self`. The parallel is close enough that CGP's own name for the feature — *implicit arguments* — is the same word Scala and Haskell use, and the desugaring is the same idea: the parameter disappears from the public signature and is bound from the surroundings before the body runs. The difference is where "the surroundings" live. A `using` parameter searches the implicit scope by type; an `#[implicit]` argument reads the context field of the matching name, so resolution is by *field*, decided when the context is defined, not by a compiler search at the call site. - -### A shared context value is a threaded environment - -When several pieces of code must agree on one value — the case Scala handles by a single `given Config` in scope and Haskell by the `Reader` monad — CGP has them all read the same field or the same [abstract type](../concepts/abstract-types.md) from the shared context. A configuration value read by many providers is injected once and every provider that names it receives the same one, with no coordination between them, exactly as one `given Config` serves an entire Scala call graph. CGP's shared error type is the canonical instance: every fallible provider names the context's `Self::Error`, so they all agree on one error type the context supplies once — the type-level echo of a threaded `Reader` environment, resolved at compile time and paid for at runtime with nothing. - -### Components and wiring are type classes without coherence - -A CGP [component](../concepts/consumer-and-provider-traits.md) is a type class, a provider is an instance, and wiring is the resolution step — but because a provider's `Self` is its own marker type, many providers for the same component coexist without violating coherence, which is what type-class instances cannot do. The `Comparator[Int]` that Scala can define only once, or the `Ord Int` that Haskell pins globally, becomes in CGP any number of interchangeable providers, each selected per context. The [modular serialization](../examples/modular-serialization.md) example makes the contrast concrete: `UseSerde`, `SerializeBytes`, and `SerializeWithDisplay` all serialize a `String` and overlap freely, where the equivalent overlapping type-class instances would be rejected: - -```rust -#[cgp_impl(UseSerde)] -impl ValueSerializer -where - Value: serde::Serialize, -{ /* ... */ } - -#[cgp_impl(SerializeBytes)] -impl ValueSerializer -where - Value: AsRef<[u8]>, -{ /* ... */ } -``` - -The cost of this freedom is the flip side of the type-class trade: CGP will not *find* the provider for you by type. A context names its choice in a [`delegate_components!`](../reference/macros/delegate_components.md) table, where Scala and Haskell would resolve the instance automatically from the type. What CGP loses in automatic resolution it gains in the ability to have `AppA` serialize a `Vec` as hexadecimal and `AppB` as base64 — two coherent local choices where a global instance would force one answer on both. - -## What users like and dislike - -Implicit parameters and the type classes built on them are among the most loved and most criticized features of the languages that have them, and both reactions are instructive. What users value is the erasure of boilerplate: a `Config`, a comparator, or an execution context threads through a deep call graph without appearing at every call, and type classes let one generic function work over any type that has an instance, resolved automatically. Scala programmers build whole architectures on implicits — type-class derivation, context propagation, dependency injection — precisely because the mechanism is so general, and Haskell's type classes are widely regarded as one of the cleanest solutions to ad-hoc polymorphism in any language. - -The dislike is the mirror image of the same generality: implicit resolution is hard to follow. The recurring Scala complaint is that a value appears "from nowhere," and tracking down *which* `given` was selected — or why an expected one was not — is a notorious time sink, made worse in Scala 2 by the single overloaded `implicit` keyword that Scala 3 split apart to address. Error messages when resolution fails are often opaque. Haskell's `ImplicitParams` is disliked enough to be effectively abandoned, for the concrete reasons that the constraints leak into every signature, no default can be given, and the monomorphism restriction interferes. And coherence itself, though it is what makes resolution safe, is a persistent source of friction: the orphan rule forces awkward module structure, and the one-instance-per-type limit forces `newtype` wrappers whenever a second interpretation of a type is wanted. - -## How CGP compares - -CGP makes the opposite trade from implicit resolution on the two axes that matter, and the comparison is cleanest stated as that trade. On *resolution*, implicit parameters are automatic and type-directed while CGP wiring is explicit: Scala and Haskell find the instance for you, and CGP asks you to name it in a table. On *coherence*, implicit parameters are coherent while CGP is not: the languages guarantee one instance per type and forbid overlap and orphans, and CGP permits unlimited overlapping providers and per-context choice by moving `Self` to a provider marker. Each side pays for what the other gets. The implicit-parameter languages get zero-boilerplate resolution and pay with the one-instance restriction and the "where did this come from" opacity; CGP gets many local implementations and explicit, greppable wiring and pays with the wiring itself — there is no automatic search, so the choice must be written down. - -Neither trade is strictly better, and the honest positioning names where each wins. When a program genuinely wants one canonical instance per type — one `Ord`, one `Show`, one serialization — and values automatic resolution above all, type classes are the better tool, and fighting their coherence with CGP-style machinery would be over-engineering. When a program needs several interchangeable implementations, per-deployment or per-context choice, or must implement a behavior for types and traits it does not own, CGP's explicit wiring is the better tool, and the coherence it discards was the very thing in the way. CGP's resolution being explicit is also, for its intended audience, a feature rather than a cost: the wiring table is the one place selection is decided, so the "spooky" resolution that dogs implicits is replaced by a lookup a reader can point at. - -## Presenting CGP to someone who knows this - -A reader fluent in implicits or type classes holds most of CGP's conceptual furniture already, and the move that unlocks it is to name the correspondence outright: a **component is a type class**, a **provider is an instance**, **wiring is instance resolution**, and an **`#[implicit]` argument is a `using` parameter** whose value comes from a context field. The context itself is the implicit environment they already reason about — the `Reader` they thread, the set of `given`s in scope — reified as a single explicit type that every provider receives automatically. Framed this way, CGP is not a foreign paradigm but their own implicit machinery with the resolution step made visible. - -The one thing to correct up front is the expectation of automatic resolution. This reader will assume CGP finds the provider by type the way their compiler finds an instance, and it does not — wiring is written by hand in a table. Present that not as a missing feature but as the deliberate consequence of the capability they will find most striking: because CGP does not resolve by type, it is free of coherence, so it can host the overlapping instances their language forbids. Lead with the pain coherence causes them — the `newtype` dance to get a second `Ord`, the orphan-rule contortions to add an instance for a foreign type, the single global choice forced on unrelated code — and show that each disappears when providers are distinct marker types selected per context. For the Scala reader specifically, the resonant pitch is "implicits without the mystery": the value still arrives without being threaded by hand, but *which* implementation was chosen is a line in a wiring table rather than the outcome of a scope search, so the debugging nightmare they know is designed out. For the Haskell reader, the pitch is "type classes without the orphan rule, and overlapping instances made legal." - -The analogy to avoid is promising that CGP "just finds the right implementation." It does not, and a reader sold on automatic resolution will feel misled the first time they have to write a `delegate_components!` entry. Set the expectation honestly — you name the provider, once, per context — and pair it immediately with what that explicitness buys: no ambiguity, no hidden priority rules, no coherence straitjacket, and the same implementation-hiding decoupling delivered through a table they can read. A reader who has spent an afternoon debugging an implicit resolution will hear the trade as a good one. - -## Sources - -The account of the related work draws on the official language documentation, primary references on type-class semantics, and community writing on the ergonomics of implicits, cited where a specific claim rests on one. - -- [Scala 3 Book — Context Parameters](https://docs.scala-lang.org/scala3/book/ca-context-parameters.html) and [Contextual Parameters (Tour of Scala)](https://docs.scala-lang.org/tour/implicit-parameters.html) — the authoritative description of `using` clauses, `given` instances, and type-directed resolution. -- [Scala 3 Implicit Redesign (Baeldung)](https://www.baeldung.com/scala/scala-3-implicit-redesign) — the rename from Scala 2's overloaded `implicit` keyword to `given`/`using` and the reasons for it. -- [GHC User's Guide — Implicit Parameters](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/implicit_parameters.html) and [Implicit parameters (HaskellWiki)](https://wiki.haskell.org/Implicit_parameters) — the `?x` constraint syntax, `let`-binding, propagation semantics, and the noted limitations that keep the feature little-used. -- [Type class (Wikipedia)](https://en.wikipedia.org/wiki/Type_class) and [Implementing, and Understanding Type Classes (okmij.org)](https://okmij.org/ftp/Computation/typeclass.html) — type classes as dictionary-passing elaboration and how instances are resolved. -- [Type classes: confluence, coherence and global uniqueness (ezyang's blog)](https://blog.ezyang.com/2014/07/type-classes-confluence-coherence-global-uniqueness/) and [Coherence of Type Class Resolution (Bottu et al.)](https://xnning.github.io/papers/coherence-class.pdf) — the definition of coherence and why it constrains instances to one per type. diff --git a/docs/related-work/ml-modules.md b/docs/related-work/ml-modules.md deleted file mode 100644 index 7f02c5ae..00000000 --- a/docs/related-work/ml-modules.md +++ /dev/null @@ -1,189 +0,0 @@ -# ML modules and modular implicits - -ML modules are the signature/structure/functor system of OCaml and Standard ML — interfaces that specify types and values, implementations that satisfy them, and functors that turn one module into another — and *modular implicits* is the OCaml extension that adds type-directed, type-class-style resolution on top of that system. CGP's components, providers, and higher-order providers line up closely with signatures, structures, and functors, but CGP replaces the manual, ordered functor application that assembling a large ML program requires with a declarative wiring table that the compiler resolves — landing somewhere between raw functors and modular implicits, and adding per-context selection that neither of them has. - -## Purpose - -ML modules solve the problem of building large programs out of interchangeable, separately-specified parts with genuine type abstraction. A *signature* states what a component provides — some abstract types and some operations over them — without fixing their representation; a *structure* implements a signature; and a *functor* is a module parameterized by another module, so a component can be written once against an interface and instantiated many ways. This is the ML answer to modularity, dependency injection, and data abstraction all at once, and it is widely regarded as one of the most expressive module systems any language offers ([Dreyer, *Understanding and Evolving the ML Module System*](https://people.mpi-sws.org/~dreyer/thesis/main.pdf)). - -This is the same territory CGP occupies, which is why the comparison is close rather than incidental. A CGP [component](../reference/macros/cgp_component.md) is an interface, a [provider](../reference/macros/cgp_impl.md) is an implementation, a [higher-order provider](../concepts/higher-order-providers.md) is a parameterized implementation, and [abstract-type components](../concepts/abstract-types.md) are exactly the abstract types a signature declares. The two paradigms diverge on *how a program is assembled from these parts* and *how the parts are selected*: ML makes you apply functors by hand in dependency order and select modules by name, whereas CGP resolves the dependency graph through the trait system from a declarative table keyed on the context. Modular implicits sits between them — it keeps ML's modules but adds automatic, type-directed selection — so the honest comparison runs across all three. It also connects to the [dependency-injection](dependency-injection.md), [implicit-parameters](implicit-parameters.md), and [algebraic-effects](algebraic-effects.md) documents, because ML modules, type classes, implicits, and CGP are one family of answers to the same question of how to choose an implementation. - -## The concept in depth - -ML modules layer three constructs — signatures, structures, and functors — on top of the core language, add abstract types and the sharing constraints that reconcile them, and distinguish applicative from generative functor application; modular implicits then adds implicit resolution over the whole thing. The subsections build up in that order, using OCaml syntax throughout, and close on the theoretical result that ties modules to type classes and thereby to CGP. - -### Signatures, structures, and abstract types - -A *signature* is an interface that specifies types and values, and a *structure* is an implementation that satisfies it, with a signature's *abstract types* giving true data abstraction. A signature that specifies an abstract type `t` and a `compare` over it, and a structure implementing it, read as: - -```ocaml -module type OrderedType = sig - type t - val compare : t -> t -> int -end - -module StringCompare = struct - type t = string - let compare = String.compare -end -``` - -The `type t` in the signature, left without a definition, is the crux of ML data abstraction: when a structure is *sealed* with a signature that keeps `t` abstract, clients cannot see the representation, so the module is free to change it, and the type system enforces representation independence ([*Modules and Data Abstraction in OCaml*](https://cs.wellesley.edu/~cs251/s12/handouts/modules.pdf)). This *sealing* is a stronger form of abstraction than mere genericity — it hides a *known* type's representation, not just abstracts over an unknown one. - -### Functors: modules parameterized by modules - -A *functor* is a function from modules to modules, and it is how ML expresses a reusable, parameterized component. A functor takes a module of some signature and produces a module built on top of it: - -```ocaml -module Make (O : OrderedType) = struct - (* ... a set implementation using O.compare ... *) -end - -module StringSet = Make (StringCompare) -``` - -The application `Make (StringCompare)` is explicit and by name: the programmer chooses the argument module and writes the application. Functors are the ML mechanism for dependency injection at the module level — `Make` depends on *some* ordered type without naming which — and OCaml's standard library uses exactly this shape, `Set.Make(String)`, throughout ([OCaml, *Functors*](https://ocaml.org/docs/functors)). - -### Sharing constraints and the applicative/generative distinction - -Two subtleties make functors harder to use than the shape above suggests: sharing constraints and the applicative/generative choice. When a functor result's abstract type must be known to equal some other type, the programmer writes a *sharing constraint* with `with type`, as in `Make (O) : S with type t = O.t` — a construct widely found confusing and a recurring source of type errors. Separately, applying a functor twice raises the question of whether the two results' abstract types are equal: OCaml functors are *applicative* by default, so `Make(X).t` equals `Make(X).t` for the same `X`, while SML functors are *generative*, minting fresh incompatible types on each application; OCaml marks a functor generative by giving it a final `()` argument ([OCaml manual, *First-class modules*](https://ocaml.org/manual/5.4/firstclassmodules.html); [practical example thread](https://discuss.ocaml.org/t/practical-example-of-applicative-vs-generative-functors/13777)). These are the fine-grained type-equality controls that make ML modules powerful and also make them heavy. - -### Assembling a program: manual functor plumbing - -Building a whole application from functors means applying each one by hand, in dependency order, and this plumbing is a documented pain point at scale. Because a functor argument must be supplied explicitly, a large program becomes a sequence of functor applications — `module A = MakeA (...)`, `module B = MakeB (A)`, `module C = MakeC (A) (B)`, and so on — that the programmer writes out and orders correctly. The MirageOS project makes the cost concrete: a unikernel there can reach a *functor application depth of up to 10* across *more than 70 modules*, and the community built an entire DSL, **Functoria**, whose sole purpose is to *organize functor applications*, because OCaml's module language "is much less flexible than its expression language" and cannot express the conditional, dependency-driven wiring such assembly needs ([*Programming Unikernels in the Large via Functor Driven Development*](https://arxiv.org/pdf/1905.02529)). That a mature ecosystem wrote a configuration DSL to escape manual functor application is the sharpest evidence of the limitation CGP's wiring addresses. - -### Modular implicits: type-directed resolution over modules - -*Modular implicits* extends OCaml so that module arguments can be marked implicit and resolved automatically by type, bringing type-class-style ad-hoc polymorphism to the module system. Introduced by Leo White, Frédéric Bour, and Jeremy Yallop, it lets a function take an implicit module parameter, written in curly braces, and lets the caller omit it; the compiler searches the implicit modules in scope for one of the required signature ([White, Bour & Yallop, *Modular implicits*](https://arxiv.org/abs/1512.01895)): - -```ocaml -module type SERIALIZABLE = sig - type t - val to_string : t -> string -end - -let to_string (type a) {S : SERIALIZABLE with type t = a} = S.to_string - -implicit module SInt : SERIALIZABLE with type t = int = struct - type t = int - let to_string = string_of_int -end - -implicit module SList {A : SERIALIZABLE} : SERIALIZABLE with type t = A.t list = struct - type t = A.t list - let to_string l = "[" ^ String.concat ";" (List.map A.to_string l) ^ "]" -end -``` - -At a call site, `to_string 2` resolves the implicit to `SInt`, and `to_string [2; 3]` resolves it to the *implicit functor* `SList` applied to `SInt` — recursive resolution that constructs the needed module by applying implicit functors, the module-system counterpart of Haskell resolving `Show [Int]` from `Show Int` ([tycon, *First-Class Modules and Modular Implicits*](https://tycon.github.io/modular-implicits.html)). It is motivated by OCaml's lack of ad-hoc polymorphism — the notorious separate `+` and `+.`, `print_int` versus `print_string` — and elaborates into OCaml's existing first-class functors, so it adds no new runtime mechanism. It remains an [experimental fork](https://github.com/ocamllabs/ocaml-modular-implicits), never merged into mainline OCaml. - -### Classes as signatures: the modular-type-classes result - -The mapping this comparison rests on — component to signature, provider to structure, higher-order provider to functor — is a known theoretical result, not an improvisation. Dreyer, Harper, and Chakravarty's *Modular Type Classes* treats **classes as signatures and instances as structures and functors**, which is exactly the correspondence CGP reuses ([Dreyer, Harper & Chakravarty, *Modular Type Classes*](https://people.mpi-sws.org/~dreyer/papers/mtc/main-long.pdf)). That result belongs as much to the [type classes](type-classes.md) comparison as to this one, and is developed there in full — including the *canonicity*-versus-modularity tension it exposes and how CGP resolves it by abandoning canonicity for per-context selection, as [bypassing coherence](../concepts/coherence.md) describes. Here it is enough that the module side of the mapping has a firm footing: a CGP component really can be read as a signature and a provider as a structure or functor. - -## How CGP expresses it - -CGP reproduces the signature/structure/functor triad with its consumer/provider machinery, then replaces manual functor application with a declarative table. The correspondence is close enough to translate construct by construct, and the two genuine differences — declarative wiring in place of ordered functor application, and per-context selection in place of by-name linking or implicit search — are exactly where CGP earns its keep. - -### Components are signatures; providers are structures - -A CGP component is a signature and a provider is a structure implementing it, matching the modular-type-classes mapping directly. Declaring a component states the interface, and a provider supplies the implementation: - -```rust -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} -``` - -`CanCalculateArea` is the signature — the operations a caller may invoke — and `RectangleArea` is a structure ascribing to it. The one piece with no ML counterpart is CGP's [consumer/provider duality](../concepts/consumer-and-provider-traits.md): CGP splits the trait into a consumer side callers use and a provider side implementers target, whereas an ML signature is a single interface used from both sides. That split exists because Rust has coherence and ML does not — ML lets a program hold many structures of one signature simply by *naming them*, so it never needs the maneuver CGP uses to escape the one-implementation-per-type rule. A CGP component is also typically a *small* signature — one capability — where an ML signature bundles many values and types; a rich ML signature corresponds to a bundle of CGP components combined through supertraits and [`#[uses]`](../reference/attributes/uses.md). - -### Abstract-type components are a signature's abstract types - -An [abstract-type component](../concepts/abstract-types.md) is CGP's version of a signature's abstract `type t`, defined with [`#[cgp_type]`](../reference/macros/cgp_type.md): - -```rust -#[cgp_type] -pub trait HasErrorType { - type Error: Debug; -} -``` - -`HasErrorType` declares an abstract `Error` the way `OrderedType` declares an abstract `t`, and generic code refers to it as `Self::Error` without naming a concrete type, just as functor bodies refer to `O.t`. The abstraction here is the *parametric* kind — generic code is polymorphic over the type it is given — which matches how a functor body abstracts over its argument's types. What CGP does *not* reproduce is ML's *sealing*: once a context wires `Error` to `anyhow::Error`, there is no type-system boundary hiding that representation from other code, so CGP leaves representation-hiding to Rust's ordinary module privacy rather than to the abstract-type mechanism. CGP abstract types defer and configure a type; ML abstract types can additionally seal one. - -### Higher-order providers are functors - -A [higher-order provider](../concepts/higher-order-providers.md) is a functor: a provider parameterized by another provider, exactly as a functor is a module parameterized by another module. A `ScaledArea` provider takes an inner calculator and builds on it: - -```rust -#[cgp_impl(new ScaledArea)] -#[use_provider(InnerCalculator: AreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - InnerCalculator::area(self) * scale_factor * scale_factor - } -} -``` - -`ScaledArea` is `Make (StringCompare)` in CGP form — a parameterized implementation applied to a specific argument. Two differences distinguish it from a plain functor, and both favor CGP's ergonomics. First, a higher-order provider can default its inner parameter to [`UseContext`](../reference/providers/use_context.md), so an unparameterized `ScaledArea` falls back to whatever the context itself wires for that component — an *open, recursive* application that a plain functor, which always demands its argument explicitly, cannot express without recursive modules. Second, CGP sidesteps the sharing-constraint problem entirely: where an ML functor needs `with type t = O.t` to make its result's abstract types line up with its argument's, CGP's abstract types are supplied by the *shared context* through `HasType`, so every provider in a context automatically agrees on `Self::Error` and `Self::Scalar` with no sharing annotation to write. - -### `delegate_components!` replaces manual functor application - -The decisive difference is that CGP wires a program declaratively where ML applies functors by hand in order. A context lists its component-to-provider choices in a [`delegate_components!`](../reference/macros/delegate_components.md) table, in any order, and the trait system resolves each provider's dependencies through the context: - -```rust -delegate_components! { - App { - AreaCalculatorComponent: ScaledArea, - ErrorTypeProviderComponent: UseType, - // ... further components, in any order - } -} -``` - -This is CGP's built-in Functoria. Where MirageOS needed a separate DSL to organize functor applications up to ten deep, CGP resolves that dependency graph as ordinary trait resolution: a provider states what it needs as [impl-side dependencies](../concepts/impl-side-dependencies.md), and the compiler threads each requirement to whatever provider the context wires for it, with no application order to get right. The counterpart to ML's type-checking that a functor argument satisfies its parameter signature is [`check_components!`](../reference/macros/check_components.md), which verifies at compile time that a context supplies every capability its providers transitively need. Grouping a reusable bundle of wirings is an [aggregate provider](../concepts/aggregate-providers.md) or a [namespace](../concepts/namespaces.md) — CGP's way of packaging a sub-assembly the way a functor packages a parameterized structure. - -### Modular implicits and CGP wiring: the same problem, opposite selection - -Modular implicits and `delegate_components!` are two answers to the same limitation of raw ML modules — that assembling a program from functors is manual — but they select implementations by opposite means. Modular implicits resolves by *implicit search*: the compiler looks through the implicit modules and functors in scope for one matching the required signature, constructing it recursively, which is the type-class route and shares type classes' coherence-leaning character (a search can be ambiguous, and canonicity fights abstraction). CGP resolves by *explicit table*: a context names one provider per component, so there is no search, no ambiguity, and each context may choose differently — the per-context selection [coherence](../concepts/coherence.md) is built to allow. The deep mechanism the two share is *recursive composition*: an implicit functor like `SList {A : SERIALIZABLE}` builds `Show` of a list from `Show` of its element, exactly as a higher-order provider builds its behavior from an inner provider resolved through the context. So the honest statement is not that modular implicits *is* `delegate_components!` but that both automate the functor plumbing ML leaves manual, one by searching and one by tabulating, over the same recursive-composition core. - -## What users like and dislike - -ML modules are admired for exactly the abstraction and parameterization they were designed to provide, and the praise is long-standing. Practitioners value *genuine data abstraction* — sealing hides a representation and the type system enforces it — *functors* for writing reusable components against an interface, *separate compilation*, and *first-class modules* for the cases where an implementation must be chosen at runtime ([OCaml manual, *First-class modules*](https://ocaml.org/manual/5.4/firstclassmodules.html)). The module system is routinely described as one of the most expressive and principled in any language, and the theoretical work around it — applicative functors, 1ML, modular type classes — is a rich, respected literature. - -The complaints cluster around weight, plumbing, and the absence of implicit resolution. The most concrete is *functor boilerplate at scale*: assembling a large application means applying functors by hand in dependency order, painful enough that MirageOS built Functoria to manage it, and rooted in the *stratification* of ML — the module language is a second, weaker language layered above the expression language, without the conditionals and flexible dependency handling ordinary code enjoys ([*Functor Driven Development*](https://arxiv.org/pdf/1905.02529); [Rossberg, *1ML — core and modules united*](https://www.cambridge.org/core/journals/journal-of-functional-programming/article/1ml-core-and-modules-united/47B10882829E4B32F98FBA93B28CEF30)). *Sharing constraints* (`with type`) are a recurring source of confusion, and the *applicative/generative* distinction is a subtlety that trips people. Above all, base ML has *no ad-hoc polymorphism*: everything must be named and applied explicitly, the very gap that motivated modular implicits, whose paper opens on OCaml's separate `+` and `+.` and its family of `print_*` functions ([White, Bour & Yallop 2015](https://arxiv.org/abs/1512.01895)). Modular implicits itself is liked for bringing type-class ergonomics to OCaml by reusing the module system rather than bolting on a separate class construct, and for naturally supporting inheritance, constructor classes, and associated types — but it is disliked, or at least unavailable, for the plainest of reasons: it remains an experimental fork and has never landed in mainline OCaml, and its own authors note that canonicity cannot be fully reconciled with modular abstraction. - -## How CGP compares - -CGP keeps the ML module correspondence — component is signature, provider is structure, higher-order provider is functor — and changes the two things ML users complain about, at the cost of the one thing ML does best. On *assembly*, CGP is declarative where ML is manual: a `delegate_components!` table lists choices in any order and the compiler resolves the dependency graph, so there is no functor-application chain to write or order, and no need for a Functoria-style DSL because CGP's wiring already is one. On *selection*, CGP is per-context and coherence-free where ML is by-name-explicit and modular implicits is implicit-search: two contexts wire the same component to different providers with no conflict, no ambiguity, and no canonicity constraint. And on *stratification*, CGP has none — providers and contexts are ordinary Rust types and traits, wired in ordinary Rust, so there is no separate, weaker module language above the expression language. What CGP gives up is ML's *sealing*: CGP's abstract types defer and configure a type but do not hide a representation behind a type-system boundary, so representation hiding falls to Rust's module privacy rather than to the abstraction mechanism itself. - -The costs beyond sealing are worth naming plainly. ML modules are separately compiled and their functor applications are simple, legible module expressions; CGP's wiring is trait resolution, which compiles to zero-overhead direct calls but produces the verbose, generated-type errors the [check traits](../concepts/check-traits.md) exist to tame. ML's first-class modules let an implementation be chosen at runtime from a value; CGP resolves at compile time and monomorphizes, so a genuinely runtime-chosen implementation needs a different tool. And ML's explicit, by-name linking gives a control and predictability that some programs want, where CGP's table-driven resolution is more automatic but less locally obvious. - -Neither is uniformly better, and the honest positioning names where each wins. When a program needs true representation hiding enforced by the type system, separately-compiled modules, runtime selection via first-class modules, or the explicit control of by-name linking, ML modules are the better tool, and reaching for CGP would forgo abstraction guarantees ML provides natively. When a program needs many interchangeable implementations chosen per deployment, a dependency graph the compiler wires rather than the programmer, abstract types unified into the same selection mechanism, and all of it in one language with no module stratum — and can accept Rust as the platform — CGP delivers the functor-and-signature style of modularity without the manual plumbing, and does so as a working library where modular implicits, the ML feature aimed at the same ergonomics, is still an unmerged fork. - -## Presenting CGP to someone who knows this - -A reader fluent in ML modules holds most of CGP's structure already, and the fastest way in is to translate the vocabulary and then flag the two changes. A **component is a signature**, a **provider is a structure**, a **higher-order provider is a functor**, an **abstract-type component is a signature's abstract `type t`**, and **`delegate_components!` is the functor-application configuration** — the thing they would otherwise write by hand or reach for Functoria to manage. **`check_components!`** is the check that a module satisfies the signature its consumer requires, moved to the wiring site. Framed this way, CGP is not a foreign paradigm but the signature/structure/functor style they know, with the assembly step turned from a manual functor chain into a declarative table. - -Two expectations to correct up front, because an ML reader will carry both. The first is *application order*: this reader expects to apply functors themselves, in dependency order, and CGP does not work that way — the table is unordered and the compiler resolves each provider's dependencies through the context, so the "wire A before B before C" discipline simply does not exist. Present that as the payoff, not a loss of control: it is Functoria's job done by the type system. The second is *abstraction*: this reader expects an abstract type to be *sealed*, its representation hidden by the signature, and CGP's abstract types are not sealed — they defer and configure a type but do not hide a known one, so representation hiding is Rust's module privacy, separate from the abstract-type mechanism. Say this plainly, because a reader who assumes sealing will look for a guarantee CGP does not make through this feature. - -The analogy to reach for, especially with an OCaml reader who has felt the pain, is *functors with the plumbing automated*. If they have wired a MirageOS unikernel or wished for modular implicits, the pitch lands: CGP is the functor-and-signature discipline with declarative, compiler-resolved wiring, per-context selection instead of a canonical-instance search, and no separate module language — and unlike modular implicits, it is not an experimental fork but a library on stable Rust. For the reader who knows *Modular Type Classes*, the sharpest framing is that CGP takes that paper's mapping — classes as signatures, instances as structures and functors — and makes the *explicit-linking* default the whole design, dropping canonicity in favor of a per-context table, which is precisely the choice that lets it host the overlapping implementations coherence would forbid. - -## Sources - -The account of the related work draws on the OCaml documentation, the primary literature on ML modules and their relationship to type classes, and the modular-implicits proposal and its implementation; the CGP snippets are drawn from the knowledge base's [consumer/provider](../concepts/consumer-and-provider-traits.md), [abstract types](../concepts/abstract-types.md), and [higher-order providers](../concepts/higher-order-providers.md) documents and verified against current macro behavior. - -- [OCaml — *Functors*](https://ocaml.org/docs/functors) and [OCaml manual — *First-class modules*](https://ocaml.org/manual/5.4/firstclassmodules.html) — the concrete syntax of signatures, structures, functors, functor application, `with type` sharing constraints, and the applicative/generative distinction. -- [*Modules and Data Abstraction in OCaml* (Wellesley CS251)](https://cs.wellesley.edu/~cs251/s12/handouts/modules.pdf) and [*A Crash Course on ML Modules*](https://jozefg.bitbucket.io/posts/2015-01-08-modules.html) — signatures as interfaces, structures as implementations, and sealing for data abstraction. -- [Dreyer, *Understanding and Evolving the ML Module System* (PhD thesis)](https://people.mpi-sws.org/~dreyer/thesis/main.pdf) and [Rossberg, *1ML — core and modules united*](https://www.cambridge.org/core/journals/journal-of-functional-programming/article/1ml-core-and-modules-united/47B10882829E4B32F98FBA93B28CEF30) — the depth of the ML module system and the critique of its stratification from the expression language. -- [Dreyer, Harper & Chakravarty, *Modular Type Classes* (POPL 2007)](https://people.mpi-sws.org/~dreyer/papers/mtc/main-long.pdf) — classes as signatures, instances as structures and functors, canonical instances for implicit resolution, and the canonicity-versus-abstraction tension. -- [White, Bour & Yallop, *Modular implicits* (ML Workshop 2014)](https://arxiv.org/abs/1512.01895) ([Cambridge PDF](https://www.cl.cam.ac.uk/~jdy22/papers/modular-implicits.pdf)), the [experimental fork](https://github.com/ocamllabs/ocaml-modular-implicits), and [tycon, *First-Class Modules and Modular Implicits in OCaml*](https://tycon.github.io/modular-implicits.html) — implicit module parameters, implicit functors, recursive resolution, and the ad-hoc-polymorphism motivation. -- [*Programming Unikernels in the Large via Functor Driven Development* (MirageOS / Functoria)](https://arxiv.org/pdf/1905.02529) — the functor-application boilerplate at scale (depth up to 10, 70+ modules) and the DSL built to manage it. -- [Applicative vs. generative functors (OCaml discussion)](https://discuss.ocaml.org/t/practical-example-of-applicative-vs-generative-functors/13777) — the practical distinction and its type-equality consequences, tracing to Leroy's module design. diff --git a/docs/related-work/reflection.md b/docs/related-work/reflection.md deleted file mode 100644 index d9869358..00000000 --- a/docs/related-work/reflection.md +++ /dev/null @@ -1,249 +0,0 @@ -# Reflection and compile-time introspection - -Reflection is the ability of a program to inspect a type's own structure — its fields, its variants, their names and types — and act on it generically, whether that inspection happens at runtime through a type descriptor a framework walks (Bevy, Go, Java) or at compile time through introspection the compiler evaluates away (Zig's `comptime`, C++26, D, and the emerging Rust nightly reflection). CGP reaches the same destination as compile-time reflection — one generic routine that works over any type's fields — but takes the structure not as *data a routine inspects* but as *types the trait system resolves against*, so its "reflection" is encoded in the type system, checked when the generic code is written rather than when it is instantiated, and erased before the program runs. - -## Purpose - -Reflection solves the problem of writing one piece of code that works over the *shape* of a type it was not written for. A serializer, a dependency-injection container, an ORM, a scene editor, a configuration loader, a debugger's pretty-printer — each needs to walk an arbitrary type's fields by name and type without the type's author having hand-written support for it. Reflection supplies that by making a type's structure available as something the program can read: a `reflect.Type` value in Go, a `Class` in Java, a `TypeInfo` in Bevy, a `std.builtin.Type` in Zig, a `std::meta::info` in C++26, a `core::mem::Type` in nightly Rust. Generic code then loops over that description — over the fields, the variants, the methods — and does its job for any type at once, instead of the type's author writing the same boilerplate again and again. - -This is the same payoff CGP's [extensible-data machinery](../concepts/extensible-records.md) delivers, which is why the comparison is foundational rather than incidental: CGP's own account of extensible records says the derive "brings the row-polymorphism of languages like PureScript and the structural typing of records to Rust," and structural, name-driven access to a type's fields is exactly what reflection provides. Where they diverge is *what the structure is made into* and *when the access is checked*. A reflection facility turns the structure into a value — a descriptor a routine inspects at runtime or in a `comptime` block; CGP turns it into a *type* — a type-level list a generic routine resolves against through trait recursion, checked by the compiler at the definition site. This document meets the reader who thinks in `TypeInfo`, `@typeInfo`, and `#[derive(Reflect)]`, gives Bevy, Zig's `comptime`, and Rust's nightly reflection MVP full treatment, and then shows where CGP's type-level mirror of reflection lands — grounded in a real worked example, [`cgp-serde`](https://github.com/contextgeneric/cgp-serde)'s reflection-driven serializer, set against facet and the Rust MVP. It is the introspection counterpart to the runtime-mechanism comparison in [dynamic dispatch](dynamic-dispatch.md), and it leans on [row polymorphism](row-polymorphism.md) for the structural-typing theory underneath. - -## The concept in depth - -Reflection spans a spectrum a reader should keep ordered, from fully runtime to fully compile-time, because CGP sits at one precise end of it. At the runtime end, a type carries metadata the program inspects while it runs — Java's `Class`, Go's `reflect.Type`, C#'s reflection with attributes, Python's pervasive `__dict__`/`getattr`, and Bevy's `bevy_reflect`. In the middle, a derive generates a static descriptor that runtime code walks without re-monomorphizing, the approach Rust's facet takes. At the compile-time end, the compiler evaluates the introspection during compilation and emits only the specialized result — Zig's `comptime`, D's `__traits` and CTFE, C++26's static reflection (P2996, [voted into C++26 in June 2025](https://isocpp.org/files/papers/P2996R13.html), with its `^^` reflection operator, `std::meta::info` values, and `[: :]` splicers), and now the Rust nightly effort. The three systems this document treats in full — Bevy at the runtime end, Zig's `comptime` as the compile-time model, and Rust's MVP as the effort bringing that model to Rust — span the whole spectrum, and CGP sits just past the compile-time end of it, where the structure is not even a compile-time *value* but a *type*. - -The uses are the same all along the spectrum, which is why the comparison to CGP is about mechanism rather than purpose. Runtime and compile-time reflection alike power serialization (Go's `encoding/json` on struct tags, Java's Jackson, serde's derive, facet), dependency injection and frameworks (Spring reflecting over annotations), object-relational mapping (Hibernate), and tooling (editors, debuggers, test discovery). CGP's extensible-data machinery targets the same jobs — [serialization](https://github.com/contextgeneric/cgp-serde), builders that assemble a struct from independent parts, visitors over an enum's variants — so the question this document answers is not *what* reflection is for but *how* each system makes a type's shape available, and what that choice costs and guarantees. - -## Runtime reflection: Bevy - -Bevy's [`bevy_reflect`](https://docs.rs/bevy_reflect/latest/bevy_reflect/) is the prominent Rust runtime-reflection system, and it is the runtime counterpart to everything CGP does statically — a full facility for inspecting, accessing, and mutating a value's structure while the program runs. It exists because a game engine must do things the type system cannot fix at compile time: load a scene from disk into components whose types are chosen by the file, drive an inspector UI over arbitrary user components, and serialize a heterogeneous world without naming every type. `bevy_reflect` gives Rust — a language with no built-in reflection — that capability as a library, built from a derive, a trait, a descriptor, and a runtime registry. - -### The `Reflect` trait and dynamic value access - -The `Reflect` trait is what makes a value dynamically inspectable, and it is layered on a weaker `PartialReflect`. A type opts in with `#[derive(Reflect)]`, after which a value can be handled as a `Box` and accessed *by string field name* — reading or writing `"health"` on a component whose concrete type the calling code never mentions — and, because `Reflect` is stronger than `PartialReflect`, *downcast back to its concrete type at runtime* ([Bevy reflection overview, Tainted Coders](https://taintedcoders.com/bevy/reflection); [`Reflect` trait docs](https://docs.rs/bevy/latest/bevy/reflect/trait.Reflect.html)). This is the defining runtime move: the type is erased into `dyn Reflect`, and its structure is recovered by asking the value at run time rather than by knowing it at compile time. Field access by name returns an `Option`, so a wrong name is a runtime `None`, not a compile error. - -### `TypeInfo`: the static shape descriptor - -Backing the dynamic access is `TypeInfo`, a description of a type's shape that is available without an instance. The `#[derive(Reflect)]` macro generates a [`TypeInfo`](https://docs.rs/bevy/latest/bevy/reflect/enum.TypeInfo.html) — a `StructInfo`, `EnumInfo`, `TupleStructInfo`, and so on — enumerating the type's fields or variants with their names and registered types, "compile-time type information ... accessible at runtime without requiring an instance of the type." This is the descriptor a generic routine walks: a serializer iterates a `StructInfo`'s fields, a UI builds one widget per named field, and each is written once against `TypeInfo` rather than per concrete type. It is the runtime-reflection analogue of Zig's `@typeInfo` result and of CGP's `Fields` type — the same field-and-variant listing, made into a value read at run time. - -### The type registry and what it powers - -The piece that turns per-type descriptors into a working framework is the runtime `TypeRegistry`. The derive also generates a `GetTypeRegistration` impl, and registering a type stores its `TypeInfo` together with associated `TypeData` — extra per-type function tables, such as how to serialize it or reflect it as a component — in a [`TypeRegistry`](https://docs.rs/bevy/latest/bevy/reflect/struct.TypeRegistry.html), which Bevy keeps in the world as the `AppTypeRegistry` resource. With a type registered, Bevy can look it up by name or `TypeId` at run time, serialize an arbitrary registered component into a scene through a `DynamicSceneBuilder`, deserialize a scene back into live components, and drive an editor over types it was never specialized for. The registry is exactly what a compile-time system does not need and cannot have: a runtime table mapping types to their structure and behavior, consulted while the program runs. - -### The cost and the safety surface - -The power of runtime reflection is paid for in cost and safety, and Bevy's shape shows both plainly. Every reflective access goes through dynamic dispatch on `dyn Reflect`, a downcast, or a registry lookup keyed by type, none of which inline and each of which adds runtime work — the same overhead that makes reflection-based serialization a known hot-path cost elsewhere. And because access is by string name against a value whose type is erased, a mismatch — a renamed field, a type never registered, a wrong downcast — surfaces at run time as a `None`, an error, or a panic, rather than as a compile error. This is the runtime end of the spectrum in full: maximal flexibility, including over types discovered at run time, bought with runtime cost and runtime failure modes. It is what CGP trades entirely away. - -## Compile-time metaprogramming: Zig's `comptime` - -Zig's `comptime` is the reference design for compile-time reflection, and it is the closest functional analogue of what CGP achieves: the introspection runs during compilation and leaves zero trace in the program. Where Bevy inspects a value at run time, Zig inspects a *type* at compile time and emits only the specialized result, so a generic-over-structure routine compiles to exactly the code a hand-written one would. `comptime` is not a reflection API bolted onto the language but a general capability — running ordinary Zig code at compile time — of which type introspection is one use, which is precisely the generality the Rust effort admires and cannot yet match. - -### `comptime` values and parameters - -The foundation is that Zig can evaluate code and pass values — including types, which are first-class `comptime` values — at compile time. A parameter marked `comptime` must be known at compile time, and because a *type* is an ordinary `comptime` value of type `type`, a function can take a type as a parameter and compute with it during compilation ([Comptime, zig.guide](https://zig.guide/language-basics/comptime/)). Generic functions in Zig are just functions with `comptime` type parameters; there is no separate generics feature. This is the substrate reflection runs on: since types are values the compiler can inspect and manipulate, introspecting one is just calling a builtin on a `comptime` value. - -### `@typeInfo`: a type's structure as comptime data - -The builtin `@typeInfo(T)` decomposes a type into structured `comptime` data describing its shape. It returns a `std.builtin.Type` — a tagged union whose active field tells whether `T` is a `.Struct`, `.Enum`, `.Union`, `.Pointer`, `.Int`, and so on, each carrying the relevant details: a `.Struct` carries a `fields` array, each field a name, a type, and a default ([Zig type introspection, DeepWiki](https://deepwiki.com/ziglang/zig/4.3-type-introspection-and-metaprogramming); [*Zig Reflection: The @typeInfo Guide*](https://topictrick.com/blog/zig-type-reflection-typeinfo)). This is Zig's `TypeInfo`, but it is a *compile-time* value: it exists only during compilation, and code that reads it is evaluated away. It is the direct ancestor of Rust's nightly `Type`/`TypeKind` and the compile-time twin of Bevy's runtime `TypeInfo`. - -### `inline for` and `@field`: iterating the shape - -Reflection becomes useful through `inline for`, which unrolls a loop over a `comptime` collection, and `@field`, which accesses a field by a `comptime`-known name. The canonical pattern reads a type's fields with `@typeInfo` or `std.meta.fields`, iterates them with an unrolled `inline for`, and reaches each field's value with `@field`: - -```zig -fn jsonStringify(value: anytype, writer: anytype) !void { - inline for (std.meta.fields(@TypeOf(value))) |field| { - try writer.print("\"{s}\": ", .{field.name}); - try jsonStringify(@field(value, field.name), writer); - } -} -``` - -This writes one serializer that works over any struct, and because `inline for` unrolls at compile time and `@field` resolves statically, it compiles to exactly the code a hand-written serializer would — "the expressiveness of runtime reflection with the performance of hand-written code" ([*Compile-Time Reflection with @typeInfo*](https://hive.blog/hive-196387/@scipio/learn-zig-series-32-compile-time-reflection-with-typeinfo)). The `field.name` is the reflected field name recovered as a compile-time string, the exact counterpart of what every reflection system stores and of CGP's `Tag::VALUE`. - -### `@Type`: reflection in reverse - -Zig's introspection runs in both directions, which is a capability most reflection facilities lack: `@Type` *constructs* a type from a `std.builtin.Type` value. Where `@typeInfo` reads a type into data, `@Type` writes data back into a type, so a program can compute a new struct's field list at compile time and materialize it as a real type ([Zig type introspection, DeepWiki](https://deepwiki.com/ziglang/zig/4.3-type-introspection-and-metaprogramming)). This is genuine compile-time type synthesis — the analogue of C++26's splicers — and it is something CGP cannot do: CGP can build type-level *lists* and partial-record companions, but it cannot synthesize a brand-new nominal type from computed structure. - -### Zero cost, checked at instantiation - -The defining trade of `comptime` is that it is zero-cost but checked late. Because all of it runs at compile time, a `comptime` routine leaves no runtime metadata, no dispatch, and no cost — the payoff CGP also claims. But `comptime`, like C++ templates, is checked at *instantiation*: a generic `comptime` routine is only fully type-checked when applied to a concrete type, so an error inside it surfaces at the use site, per instantiation, in the template-error style Zig shares with C++. There is no separate declaration-site check that a `comptime` function is well-formed for all valid inputs. This is the one axis on which CGP diverges sharply from the model it otherwise mirrors, and the divergence is the subject of a later section. - -## Compile-time reflection comes to Rust - -Rust has historically had no reflection at all, and the effort to add compile-time reflection is now concrete enough to study directly from its source and tracking issues — a development that matters to CGP because its stated goal is to supply, from the compiler, exactly the structural information CGP's derives generate by hand. The picture has three parts: the userspace crates that filled the gap, the nightly compiler MVP and its current status, and the design questions still open before it can stabilize. - -### Why Rust had none, and the userspace answers - -Rust filled the absence of reflection with `#[derive]` proc macros that generate specialized code per type, and the cost of that choice is what motivates the reflection effort. The prominent userspace attempt, the [**facet**](https://fasterthanli.me/articles/introducing-facet-reflection-for-rust) crate, makes the argument sharply: serde's derives generate *monomorphized code* for each type, so `serde_json::to_string_pretty` is instantiated anew for every type — dozens of specialized copies — bloating compile times and binaries, with `syn` "often in the critical path of builds." Facet's `#[derive(Facet)]` instead generates *data*, not code: a compile-time `const SHAPE: &Shape` descriptor holding each field's name, offset, type shape, and function pointers, which generic *runtime* reflection code then walks once instead of being re-monomorphized per type. Facet is therefore runtime reflection driven by compile-time-generated const data — a point between Bevy's fully-runtime registry and Zig's fully-compile-time introspection. The narrower [`dtolnay/reflect`](https://github.com/dtolnay/reflect) is a proof of concept in a different direction: a compile-time reflection API for writing proc macros. - -### The nightly MVP: `core::mem::type_info` - -The nightly compiler effort is the more consequential development, and its status can be read directly from the source and the tracking issue. The [tracking issue #146922](https://github.com/rust-lang/rust/issues/146922) (opened September 2025, active into 2026) records the feature gate `#![feature(type_info)]` and consolidates an earlier [reflection tracking issue #142577](https://github.com/rust-lang/rust/issues/142577) that Oli Scherer (`oli-obk`) opened in June 2025 and that was closed as a duplicate. The API lives in [`library/core/src/mem/type_info.rs`](https://github.com/rust-lang/rust/blob/master/library/core/src/mem/type_info.rs) and is reached through two `const fn`s — `TypeId::info(self) -> Type` and `Type::of::() -> Type` — that return a `Type` whose single field is a `kind: TypeKind`: - -```rust -#![feature(type_info)] -use core::mem::{Type, TypeKind}; - -const KIND: TypeKind = Type::of::<(u32, bool)>().kind; // TypeKind::Tuple(..) -``` - -The MVP landed [tuples-first (PR #146923)](https://github.com/rust-lang/rust/pull/146923), but a rapid series of merged PRs has grown the surface far past that: `TypeKind` now has variants `Tuple`, `Array`, `Slice`, `DynTrait`, `Struct`, `Enum`, `Union`, `Bool`, `Char`, `Int`, `Float`, `Str`, `Reference`, `Pointer`, `FnPtr`, and `Other`, added by dedicated PRs for [ADTs — structs and enums (#151142)](https://github.com/rust-lang/rust/pull/151142), [trait objects (#151239)](https://github.com/rust-lang/rust/pull/151239), and [function pointers (#152173)](https://github.com/rust-lang/rust/pull/152173), among others. Crucially for the CGP comparison, struct and enum reflection is now real: a `Struct` exposes `fields: &'static [Field]` and its `generics`, an `Enum` exposes `variants: &'static [Variant]`, and each `Field` carries exactly the data a reflection consumer needs — `name: &'static str`, `ty: TypeId`, and `offset: usize`: - -```rust -// from library/core/src/mem/type_info.rs -pub struct Struct { - pub generics: &'static [Generic], - pub fields: &'static [Field], - pub non_exhaustive: bool, -} -pub struct Field { - pub name: &'static str, - pub ty: TypeId, - pub offset: usize, -} -``` - -Beyond the shape kinds, the surface already includes `TypeId::size()` and `TypeId::variants()` convenience queries, generics reflected as a `Generic` enum (`Lifetime`, `Type`, `Const`), and trait reflection through [`TypeId::trait_info_of` (PR #152003)](https://github.com/rust-lang/rust/pull/152003) returning a `TraitImpl` whose `get_vtable()` yields the `DynMetadata`. The current state, then, is not "tuples or bust" as the earliest write-ups described but a broad reflection surface covering nearly every type kind — with the remaining work being stabilization, not more kinds. - -### Compile-time-only: the `#[rustc_comptime]` restriction - -The single most important design decision is that this reflection is callable *only at compile time*, and the source enforces it explicitly. `TypeId::info` is documented "It can only be called at compile time," and the query methods carry a `#[rustc_comptime]` attribute — a compiler marker that pins them to compile-time evaluation. The reason is stated in the [project goal](https://rust-lang.github.io/rust-project-goals/2026/reflection-and-comptime.html): exposing type metadata to runtime code would force a global type table into every binary, the very cost that makes runtime reflection expensive. By restricting reflection to `const` contexts, Rust keeps the zero-cost property — the reflection is evaluated during compilation and nothing about it survives into the running program, exactly as in Zig `comptime`. A visible tension the source flags is lifetimes: `Type::of` can yield `TypeId`s "not necessarily derived from types that outlive `'static`," which "will be able to break invariants that other `TypeId` consuming crates may have assumed," an interaction with the non-`'static`-`TypeId` question ([#41875](https://github.com/rust-lang/rust/issues/41875)) still under discussion. - -### How Zig's `comptime` inspired it — and why Rust chose const-fn reflection - -The relationship to Zig's `comptime` is one of inspiration without adoption, and the project goal is explicit about it. Zig demonstrated that first-class compile-time type introspection combined with compile-time code execution can subsume both runtime reflection and macro-based codegen at zero cost, which is the capability the Rust effort wants — the goal is even named "reflection *and comptime*," and the `#[rustc_comptime]` marker borrows the word. But the goal **deliberately declines full Zig-style `comptime` for now**, "because the compiler is not set up in a way to permit proc macros from accessing type information from the current crate," treating general compile-time execution as future work that would need "more than 5 years" of compiler restructuring. The pragmatic path is a `const fn` that introspects types — `comptime`'s ambition narrowed to what Rust's architecture can deliver near-term: reflection first as a const API, general `comptime` much later, with Zig as the acknowledged model rather than the immediate target. - -### The road to stabilization: open questions and the RFC - -Despite the broad implemented surface, the feature is at the very start of the stabilization process, and the tracking issue is candid about what remains. Its checklist has every box unchecked — implement the goal, discuss general reflection with the lang team, discuss the non-`'static` question, write an RFC, update documentation, and only then a stabilization PR — and its unresolved questions cut to the foundations: whether to use many fine-grained intrinsics or one big intrinsic returning an enum, what semver guarantees reflection can retain, whether reflection should exist at all given that it "allows causing monomorphization-time errors ... for much more fine-grained details of a generic parameter `T`," whether anything lifetime-aware is possible, and whether a non-const-eval scheme (const generics, or even proc macros) would be better. The effort is driven by Oli Scherer as compiler champion with Scott McMurray (lang) and Josh Triplett (libs-api), and the plan spans 2026–2028: expand and refine the surface, validate it by giving `facet`, `bevy_reflect`, and `reflect` a nightly feature that "obsoletes having derives and makes the derives no-ops," and only then seek lang and libs-api buy-in and write the RFC. The ambition is that "crates like `bevy` will be able to 'just work' with arbitrary types instead of requiring authors to `#[derive(Component)]`." - -## How CGP expresses it - -CGP reaches compile-time reflection's payoff by a different route: it encodes a type's structure as *types* and processes them with *trait resolution*, so the "reflection" is done by the type system rather than by inspecting a descriptor. A struct's shape becomes a type-level list through a [derive](../reference/derives/derive_has_fields.md), generic code recurses over that list through trait impls the way Zig's `inline for` iterates `@typeInfo`, and the whole computation is checked at the definition site and erased before runtime. The [`cgp-serde`](https://github.com/contextgeneric/cgp-serde) crate makes the parallel concrete, and set against facet and the Rust MVP it shows exactly where the type-level encoding differs from value reflection. - -### A type's shape becomes a type, not a descriptor - -The foundational move is that `#[derive(HasFields)]` lifts a struct's structure into the type system as a type-level list, which is CGP's form of the `TypeInfo`/`Shape`/`std.builtin.Type`/`Type` descriptor the reflection systems build. Deriving it gives a struct a `Fields` associated type that is a [`Product!`](../reference/macros/product.md) of [`Field`](../reference/types/field.md) entries, and an enum a [`Sum!`](../reference/macros/sum.md) of them: - -```rust -#[derive(HasFields)] -pub struct Config { - pub host: String, - pub port: u16, -} - -// generated: -// impl HasFields for Config { -// type Fields = Product![ -// Field, -// Field, -// ]; -// } -``` - -This is the same information a reflection descriptor carries — each field's name and type — but it is a *type*, not a value. The Rust MVP's `Field` is a `struct` value with `name: &'static str` and `ty: TypeId`; CGP's `Field` is a *type* whose `Tag` is a type-level [`Symbol!`](../reference/macros/symbol.md) string and whose `Value` is the field's actual type as a type parameter. That difference — the field type carried as a *type* rather than an opaque `TypeId` value — is what lets CGP dispatch on it, and it is the crux of the comparison below. - -### `cgp-serde`: reflection-driven serialization in the trait system - -The `cgp-serde` crate's [`SerializeFields`](https://github.com/contextgeneric/cgp-serde/blob/main/crates/cgp-serde/src/providers/fields.rs) provider is a complete, working example of compile-time reflection expressed entirely through traits — one generic serializer that works over any `HasFields` type, the CGP counterpart of the Zig `jsonStringify` above. It serializes any value whose shape is known to the type system as a map, delegating to a trait that recurses over the field list: - -```rust -#[cgp_impl(new SerializeFields)] -impl ValueSerializer -where - Value: HasFields, - Value::Fields: FieldsSerializer, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer, - { - let s = serializer.serialize_map(None)?; - Value::Fields::serialize_fields(self, value, s) - } -} -``` - -The `FieldsSerializer` trait is the `inline for` of CGP — a recursion over the [`Cons`/`Nil`](../reference/types/cons.md) product spine, with a recursive-step impl for a non-empty list and a base-case impl for the empty one: - -```rust -impl FieldsSerializer - for Cons, Rest> -where - Tag: StaticString, // the field name, as a const &'static str - Value: HasField, // read this field from the value - Context: CanSerializeValue, // serialize the field through the context's wiring - Rest: FieldsSerializer, // recurse on the remaining fields -{ - fn serialize_fields(context: &Context, value: &Value, mut serializer: S) - -> Result - { - let field_value = value.get_field(PhantomData); - serializer.serialize_entry(Tag::VALUE, &SerializeWithContext { context, value: field_value })?; - Rest::serialize_fields(context, value, serializer) - } -} - -impl FieldsSerializer for Nil { /* serializer.end() */ } -``` - -Every reflection concept has a precise counterpart here. The recursion over `Cons`/`Nil` is the loop over the field list; `Tag::VALUE` — the field name recovered as a compile-time `&'static str` through the [`StaticString`](../reference/traits/static_format.md) trait — is exactly the `field.name` a reflection system reads, produced with zero runtime metadata; `value.get_field(PhantomData)` is the by-name field access, resolved statically through [`HasField`](../reference/traits/has_field.md) rather than by a runtime lookup or a `@field` builtin; and `Context: CanSerializeValue` is the *recursive* step — serializing each field's value by dispatching on the field's *type*, which routes back through the context's own serialization wiring. The deserialization dual, [`DeserializeRecordFields`](https://github.com/contextgeneric/cgp-serde/blob/main/crates/cgp-serde/src/providers/record.rs), is even more visibly reflective: it reads a field name as a runtime `String` from the input map and compares it against each field's compile-time `Tag::VALUE`, routing the value into a `SetOptional` builder — reflection reading names in both directions. - -### `cgp-serde` versus facet versus the reflection MVP - -The three approaches solve the same problem — write serialization once, not per type — and comparing them precisely shows what the type-level encoding buys and costs. The axis that organizes them is *what a type's shape is turned into, and how the generic code consumes it*: - -- **serde's derive** generates *code*: a bespoke `Serialize` impl per struct. The logic is duplicated — monomorphized — for every type through generated code, which is the compile-time and binary bloat facet objects to. -- **facet** generates *data*: a `const SHAPE: &Shape` value per type, walked by a shared *runtime* reflection routine. It avoids re-monomorphizing the serializer per type, shrinking the binary, but pays runtime reflection cost to walk the shape and dispatch through the shape's function pointers. -- **the Rust MVP** has the *compiler* provide the data: `Type::of::()` yields a `Struct` with `fields: &'static [Field]`, needing no derive at all, consumed by `const`-evaluated code at compile time. -- **`cgp-serde`** generates *type-level data*: a `Product!` of `Field` via the `HasFields` derive, walked by a shared *compile-time* routine — `FieldsSerializer` trait recursion — that monomorphizes and inlines to direct code. - -Two differences make CGP's position distinct rather than merely another point. First, the field's *type* is preserved as a *type*, not erased to a value. Facet's `Shape` and the MVP's `Field { ty: TypeId, .. }` carry the field's type as an opaque descriptor — a `Shape` pointer or a `TypeId` — from which you cannot directly instantiate a generic function, so recursion into a field's own type is mediated by stored function pointers (facet) or is not yet expressible from the raw reflection (the MVP). CGP's `Field` carries `FieldValue` as a real type parameter, so `SerializeFields` can write `Context: CanSerializeValue` and recurse into a fully-typed, statically-checked serializer for the field's type — the recursion the MVP's `TypeId` cannot yet drive. Second, `cgp-serde` dispatches each field through the *context's* `CanSerializeValue` wiring, so the same type serializes differently under different application contexts — the [per-context choice](../concepts/coherence.md) that facet, serde, and the MVP, each with one fixed interpretation per type, do not have. - -The honest costs are equally clear. Unlike facet, `cgp-serde` does *not* solve the monomorphization-bloat problem — its `FieldsSerializer` recursion instantiates per field-list, so it produces specialized code per type just as serde's output does; what it saves is the *authoring* duplication (the logic is written once) and it gains *configurability* (the wiring), not binary size. And unlike the Rust MVP, `cgp-serde` requires the `#[derive(HasFields)]` opt-in: it cannot serialize a foreign type that did not derive the machinery, exactly the restriction the MVP is designed to remove for value-reflection consumers. - -### Checked when the code is written, not when it is instantiated - -The property that separates CGP from `comptime`, from C++ templates, and from the const-fn reflection MVP is *when* generic-over-structure code is checked, and CGP checks it early. Zig's `comptime` and the MVP's `const fn` reflection are checked at *instantiation*: a `comptime` routine or a reflection-driven `const fn` is fully checked only when applied to a concrete type, so an error surfaces at the use site, per instantiation. CGP's generic code is checked at its *definition*: the `where` clause on the `FieldsSerializer` impl — `Tag: StaticString`, `Value: HasField`, `Context: CanSerializeValue` — is verified once, against the bounds, and [`check_components!`](../reference/macros/check_components.md) verifies a context supplies everything its wiring transitively needs. This is the modular type-checking that [type classes](type-classes.md) give and templates do not, and CGP inherits it because its reflection is expressed as trait bounds the compiler checks up front rather than as code re-checked at each instantiation. - -### Reflection that also selects behavior and configures types - -CGP applies the same type-level-shape idea beyond inspecting data, to two things a structural reflection facility does not directly address: choosing implementations and fixing abstract types. Reflection systems inspect a type's fields and values; some frameworks then use that to select behavior — Spring wires beans by reflecting over annotations, Bevy associates `TypeData` with registered types — so behavior selection by reflection is real, but it is a runtime, registry-mediated act layered on top of the introspection. CGP folds behavior selection into the same compile-time type-level table that carries its structural information: a [component](../reference/macros/cgp_component.md) is keyed by a type-level marker, a context's [`delegate_components!`](../reference/macros/delegate_components.md) table maps markers to providers, and the [`#[cgp_type]`](../reference/macros/cgp_type.md) machinery lets a context fix an abstract *type* — its `Error`, its `Scalar` — through that same table with [`UseType`](../reference/providers/use_type.md). The `cgp-serde` serializer shows this in action: `Context: CanSerializeValue` resolves each field's serializer through the context, so the reflection that walks the shape and the wiring that interprets each field are one mechanism. - -### What CGP cannot do - -The runtime and open-introspection powers of reflection have no CGP analogue, and the boundary is worth stating plainly. Because CGP's structural information is a type computed at compile time, it cannot inspect a value whose type is only known at runtime, downcast a `dyn` value, serialize a type discovered from a plugin loaded at startup, or drive an editor over types registered in a runtime table — all of which are exactly what `bevy_reflect` exists to do, and for which Bevy's runtime reflection, not CGP, is the right tool. CGP also cannot *construct* an arbitrary new nominal type the way Zig's `@Type` or C++26's splicers can; its type-level `Product!`/`Sum!` lists and partial-record companions are a constrained, closed form of type construction, not open type synthesis. And, decisively, CGP's structural machinery is **opt-in per type**: it works only for types that `#[derive(HasFields)]` or [`#[derive(CgpData)]`](../reference/derives/derive_cgp_data.md), so it cannot introspect a foreign type that did not derive it — the same limitation facet and `bevy_reflect` have today, and precisely the restriction the nightly reflection MVP is being built to lift, though for value consumers rather than type-level ones. - -## What users like and dislike - -Reflection is one of the most relied-upon capabilities in mainstream software, and the reasons are consistent. It lets a framework be written once and work over every user type — serialization, dependency injection, ORMs, editors, test frameworks, and configuration all lean on it — which is why Java, C#, Go, and Python ship it and why ecosystems form around it. Runtime reflection additionally works on *arbitrary* types, including ones the framework author never saw, with no cooperation from the type beyond being reflectable, and compile-time reflection promises the same generality with none of the runtime cost — Zig's users prize getting "the expressiveness of runtime reflection with the performance of hand-written code," and the facet and Rust-reflection efforts are motivated by cutting the compile-time and binary bloat that per-type derive codegen imposes ([fasterthanli.me](https://fasterthanli.me/articles/introducing-facet-reflection-for-rust)). - -The complaints split cleanly by when the reflection runs. Runtime reflection is criticized on three counts: *performance* — Go's reflection-based `encoding/json` is "fundamentally slow" because it re-inspects types on every call, and Java reflection is a well-known hot-path cost ([*The Hidden Cost of Reflection in Go*](https://dev.to/devflex-pro/the-hidden-cost-of-reflection-in-go-why-your-code-is-slower-than-you-think-41ee)); *type safety* — it "bypasses compile-time type safety" and produces "stringly typed" access where a field named by a `json:"…"` tag or a `getField("name")` call fails at runtime, "quite like a dynamically typed language" ([Go reflection guide](https://medium.com/@mojimich2015/golang-reflection-the-guide-to-runtime-type-inspection-manipulation-and-best-practices-303087684576)); and *tooling and safety* — renaming a field silently breaks reflective access, dead-code elimination must be defeated to keep metadata, and reflection can bypass access control. Java's *type erasure* adds a further limit: reflection cannot distinguish `List` from `List` at runtime because the parameter is gone ([*Linguistic Reflection in Java*](https://arxiv.org/pdf/cs/9810027)). Compile-time reflection answers the performance and much of the safety objection — the checks run before the program does — but trades them for its own costs: instantiation-time error messages in the template/`comptime` style, added compile-time work, the conceptual weight of metaprogramming, and, as the Rust tracking issue frankly asks, the risk of enabling "monomorphization-time errors ... for much more fine-grained details of a generic parameter" that make generic code fail deep inside instantiation rather than at its interface. - -## How CGP compares - -CGP and reflection make opposite choices on the two axes that organize the whole spectrum — *when the structure is available* and *what form it takes* — and the comparison is cleanest as that pair. On *timing*, runtime reflection makes structure available while the program runs, paying dispatch and inspection cost on every use and deferring errors to runtime; compile-time reflection and CGP both resolve it during compilation and pay nothing at runtime. On *form*, every reflection system — runtime or compile-time — makes the structure into *data* that code inspects: a `TypeInfo`, a `Shape`, a `std.builtin.Type`, a `Type`; CGP makes it into a *type* that trait resolution dispatches on. That second difference is what buys CGP its distinctive properties: because the structure is a type and the field types are carried as types, generic code is trait impls checked modularly at their definition rather than per instantiation, it can recurse into a field's own type with full static checking, and the same type-level encoding extends seamlessly from inspecting data to selecting providers and configuring abstract types. What CGP gives up for this is generality and runtime reach — it introspects only types that opted in by deriving its machinery, and only at compile time. - -The honest positioning names where each wins, and here it comes with an unusual twist: CGP and Rust's emerging compile-time reflection are more complementary than competing. When a program must inspect types at runtime — deserialize into a type chosen from a config file, serialize a heterogeneous registry of components, build an editor or a debugger over live values, or reflect over a foreign type that cannot be made to derive anything — runtime reflection is the right and only tool of the two, and `bevy_reflect` or facet, not CGP, is what to reach for. When a program wants generic-over-structure code that costs nothing at runtime, is checked when written, recurses into field types with full type information, and drives behavior and type selection as well as data walking, CGP delivers that on stable Rust today, as `cgp-serde` shows. The two lines could converge, but less directly for CGP than for facet or Bevy: the nightly MVP produces reflection as *const values* (`Type`, `Field`, `TypeId`), which value-reflection libraries consume natively and which the project goal aims to let them use with no derive — whereas CGP consumes reflection as *types* (`Product!`, `Field`), a different projection the const-value MVP does not yet supply. For CGP to shed its `#[derive(HasFields)]` the way the goal envisions for `#[derive(Reflect)]`, the compiler would need to expose a type's shape *as types* — a type-level counterpart to the value-level MVP — which is not part of the current scheme. Until then CGP's derive stands in for the compiler-provided reflection Rust is still building, and the reflection MVP is best read not as a rival to CGP but as the same underlying idea, reflected into values where CGP reflects it into types. - -## Presenting CGP to someone who knows this - -A reader who thinks in `TypeInfo`, `@typeInfo`, or `core::mem::Type` holds most of CGP's structural machinery already, and the way in is to map the vocabulary and then mark the one shift. A `#[derive(HasFields)]` type's `Fields` is a **compile-time type descriptor**, exactly like a `Shape`, a `std.builtin.Type`, or the MVP's `Struct { fields, .. }` — except it is a *type*, not a value; a generic impl recursing over the [`Cons`/`Nil`](../reference/types/cons.md) spine, as `cgp-serde`'s `FieldsSerializer` does, is an **`inline for` over the fields**, resolved by the trait system instead of a `comptime` loop or a const-fn walk; `Tag::VALUE` is the reflected **field name**; and [`check_components!`](../reference/macros/check_components.md) is the guarantee that the generic code type-checks for this concrete type, discharged when the code is written rather than at instantiation. Framed this way, CGP is not a foreign paradigm to this reader but *compile-time reflection encoded in the type system* — the Zig `comptime` idea they know, expressed as trait resolution over type-level shapes, with modular checking they may wish `comptime` and templates had. - -The expectation to correct at once is that CGP offers a reflection *API* — an object to inspect, a descriptor to query, a value to walk. It does not: there is no `TypeInfo` to hold, no `Field` slice to iterate imperatively, no `Type::of::()` to call. The structure is a type that trait impls dispatch on, so "reflecting" over it means writing a recursive impl, not calling `fields()`. For the Zig or C++26 reader, the sharp framing is that CGP's `Product!`/`Sum!` are the `@typeInfo` result reified as types and its trait recursion is the `inline for`, but CGP cannot `@Type`-construct arbitrary new types and cannot introspect a type that did not derive the shape — a real limit against a true reflection facility. For the Rust reflection reader specifically, the precise point is that CGP carries a field's *type as a type* where the MVP's `Field` carries it as an opaque `TypeId`, which is why CGP can recurse into a typed, checked serializer for each field and the raw MVP cannot yet — the two encode the same shape but project it into different domains, values versus types. For the Bevy or Java reader, the pitch is the pair of things runtime reflection makes them pay for and CGP does not: *no runtime cost*, because the introspection is resolved away instead of walked on every call, and *no stringly-typed runtime failure*, because a field or capability the context lacks is a compile error at the wiring site rather than a `None` or an exception in production. - -The analogy to avoid is calling CGP "a reflection system." It has no runtime type information, no descriptor to inspect, and no ability to reflect over a type that did not opt in — a reader sold on that framing will look for an introspection API and find trait bounds instead. Say precisely what CGP is: compile-time structural reflection encoded as types and resolved by the trait system, checked at the definition site, erased before runtime, carrying field types as types so it can recurse into them, and extended from inspecting a type's data to selecting its behavior and configuring its abstract types. A reader who has paid the runtime tax of reflection, or fought a template's instantiation-time errors, will hear that as a considered trade rather than a missing feature. - -## Sources - -The account of the related work draws on the official documentation and primary write-ups of each reflection system, the Rust project's own tracking issues, pull requests, and library source for its compile-time-reflection effort, and cited community writing for sentiment; the CGP snippets are drawn from the [`cgp-serde`](https://github.com/contextgeneric/cgp-serde) crate and the knowledge base's [extensible records](../concepts/extensible-records.md) and [extensible variants](../concepts/extensible-variants.md) material, verified against current macro behavior. - -- [Bevy `bevy_reflect` documentation](https://docs.rs/bevy_reflect/latest/bevy_reflect/), [`Reflect` trait](https://docs.rs/bevy/latest/bevy/reflect/trait.Reflect.html), [`TypeInfo`](https://docs.rs/bevy/latest/bevy/reflect/enum.TypeInfo.html), [`TypeRegistry`](https://docs.rs/bevy/latest/bevy/reflect/struct.TypeRegistry.html), and [Bevy Reflection (Tainted Coders)](https://taintedcoders.com/bevy/reflection) — the `Reflect`/`PartialReflect` traits, `TypeInfo`, the runtime `TypeRegistry`/`TypeRegistration` and `AppTypeRegistry`, `#[derive(Reflect)]`, downcasting, `TypeData`, and scene serialization that make Bevy the Rust runtime-reflection exemplar. -- [Zig type introspection and metaprogramming (DeepWiki)](https://deepwiki.com/ziglang/zig/4.3-type-introspection-and-metaprogramming), [Comptime (zig.guide)](https://zig.guide/language-basics/comptime/), [*Zig Reflection: The @typeInfo Guide*](https://topictrick.com/blog/zig-type-reflection-typeinfo), and [*Compile-Time Reflection with @typeInfo*](https://hive.blog/hive-196387/@scipio/learn-zig-series-32-compile-time-reflection-with-typeinfo) — `comptime` values and parameters, `@typeInfo`/`@Type`, `std.meta.fields`, `inline for`, `@field`, and the zero-runtime-cost, instantiation-checked model that inspired the other systems languages. -- [Rust tracking issue #146922 (`type_info`)](https://github.com/rust-lang/rust/issues/146922), [tracking issue #142577 (reflection, closed as duplicate)](https://github.com/rust-lang/rust/issues/142577), the source at [`library/core/src/mem/type_info.rs`](https://github.com/rust-lang/rust/blob/master/library/core/src/mem/type_info.rs), and the implementation PRs [#146923 (MVP)](https://github.com/rust-lang/rust/pull/146923), [#151142 (ADTs)](https://github.com/rust-lang/rust/pull/151142), [#151239 (trait objects)](https://github.com/rust-lang/rust/pull/151239), [#152003 (`trait_info_of`)](https://github.com/rust-lang/rust/pull/152003), and [#152173 (`FnPtr`)](https://github.com/rust-lang/rust/pull/152173) — the exact API (`Type::of`, `TypeId::info`, `TypeKind`, `Struct`/`Field`/`Variant`), the `#[rustc_comptime]` compile-time-only restriction, the current implemented surface, and the open stabilization checklist and design questions. -- [Rust Project Goals — *Reflection and comptime* (2026)](https://rust-lang.github.io/rust-project-goals/2026/reflection-and-comptime.html) and [*Compile-Time Reflection Is Finally Here*](https://weeklyrust.substack.com/p/compile-time-reflection-is-finally) — the plan to make reflection-crate derives no-ops, the explicit rejection of full Zig-style `comptime` near-term, the 2026–2028 timeline, and the leadership (Oli Scherer, Scott McMurray, Josh Triplett). -- [fasterthanli.me, *Introducing facet: Reflection for Rust*](https://fasterthanli.me/articles/introducing-facet-reflection-for-rust) and [`dtolnay/reflect`](https://github.com/dtolnay/reflect) — facet's derive-generates-data approach (a `const Shape` walked by runtime reflection) and its motivation in serde's monomorphization cost, and a compile-time reflection API for proc-macro authors. -- [`cgp-serde` `SerializeFields`](https://github.com/contextgeneric/cgp-serde/blob/main/crates/cgp-serde/src/providers/fields.rs) and [`DeserializeRecordFields`](https://github.com/contextgeneric/cgp-serde/blob/main/crates/cgp-serde/src/providers/record.rs) — the worked CGP example: a generic serializer and deserializer that recurse over a type's `HasFields` shape through trait resolution, recovering field names via `StaticString` and dispatching each field through the context's `CanSerializeValue` wiring. -- [P2996R13, *Reflection for C++26*](https://isocpp.org/files/papers/P2996R13.html) and [*Reflection in C++26 (P2996)*](https://learnmoderncpp.com/2025/07/31/reflection-in-c26-p2996/) — the `^^` reflection operator, `std::meta::info`, `consteval` metafunctions, and splicers `[: :]`, voted into C++26 in June 2025, the C++ realization of compile-time reflection. -- [Go FAQ — reflection and interfaces](https://go.dev/doc/faq), [*The Hidden Cost of Reflection in Go*](https://dev.to/devflex-pro/the-hidden-cost-of-reflection-in-go-why-your-code-is-slower-than-you-think-41ee), [*Golang Reflection guide*](https://medium.com/@mojimich2015/golang-reflection-the-guide-to-runtime-type-inspection-manipulation-and-best-practices-303087684576), and [*Linguistic Reflection in Java*](https://arxiv.org/pdf/cs/9810027) — the `reflect` package and struct tags, the runtime performance and type-safety ("stringly typed") costs, and Java's type-erasure limitation, the mainstream runtime-reflection landscape CGP contrasts with. diff --git a/docs/related-work/row-polymorphism.md b/docs/related-work/row-polymorphism.md deleted file mode 100644 index 90b9393c..00000000 --- a/docs/related-work/row-polymorphism.md +++ /dev/null @@ -1,182 +0,0 @@ -# Row polymorphism, structural typing, and extensible data types - -Row polymorphism, structural typing, and extensible data types are the family of type-system features that let code operate on data by the *shape* of its fields and variants — "any record that has a `name` field," "any enum that includes a `Circle` case" — rather than by a concrete type's declared name. CGP brings the same field-and-variant-driven extensibility to Rust's resolutely nominal structs and enums, not through a built-in row kind but by deriving a type-level view of a type's shape and matching field and variant names through the trait system at compile time. - -## Purpose - -Nominal type systems, Rust's included, force code to name the concrete type it works on, which couples every operation to a fixed shape. A Rust function that reads a `name` field must take a specific struct, a `match` must spell out one specific enum's variants, and two structs with identical fields but different names are incompatible types. This is safe and predictable, but it blocks a large class of useful code: a function that works on *any* record carrying the fields it needs, a handler that covers *one* variant of *any* enum that includes it, or a record assembled piecemeal from parts that never name the whole. The features surveyed here exist to lift that restriction — to let a type's structure, not its name, decide what operations apply. - -This is precisely the capability CGP's [extensible records](../concepts/extensible-records.md) and [extensible variants](../concepts/extensible-variants.md) add to Rust, so the comparison is not incidental but foundational: the knowledge base's own account of extensible records says the machinery "brings the row-polymorphism of languages like PureScript and the structural typing of records to Rust." A reader who already thinks in rows and structural shapes holds most of the intuition CGP's data machinery rests on. The work of this document is to make that correspondence exact — to show which CGP construct is the row variable, which is record concatenation, which is variant injection — and to be honest about the one place the analogy breaks: CGP has no row *inference*, so where a row-typed language discovers the shape for you, CGP asks you to derive it and wire it. - -## The concept in depth - -The three phrases in this document's title name overlapping but distinct ideas, and the comparison to CGP is clearest when they are kept apart. *Structural typing* is the broad principle that type compatibility is decided by structure rather than by name. *Row polymorphism* is a specific parametric mechanism — a quantified *row variable* standing for "the rest of the fields" — that achieves structural flexibility while preserving the extra fields' types. *Extensible data types* are the records and variants a row system operates on, and their common theoretical account is the subject of the research paper this document studies. The sections below build up from the general principle to the specific mechanism, work PureScript as the primary example of row polymorphism, distinguish the two ways rows are realized — as a built-in kind or as qualified-type predicates — cover the variant (sum) side, and close on the unifying theory, the third application to effects, and two cautionary tales. - -### Structural versus nominal typing - -Structural typing decides type compatibility by a type's members, not its name. A structural type system is "one in which type compatibility and equivalence are determined by the type's actual structure or definition and not by other characteristics such as its name or place of declaration"; a value of type `A` is usable where `B` is expected when `A` has all of `B`'s members with compatible types ([Wikipedia: Structural type system](https://en.wikipedia.org/wiki/Structural_type_system)). A *nominal* system is the opposite — compatibility rests on "explicit declarations and/or the name of the types," so two `struct`s with identical fields but different names are never interchangeable ([Wikipedia: Nominal type system](https://en.wikipedia.org/wiki/Nominal_type_system)). - -TypeScript is the mainstream face of structural typing. Its checker "focuses on the shape that values have," an approach it calls "duck typing or structural typing," and an object satisfies an interface merely by having the required members, with no declaration that it implements the interface ([TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-oop.html)). Crucially, structural typing is *not* the same as row polymorphism: TypeScript "doesn't have 'true' row type polymorphism in the sense of ML-family languages" and instead approximates it with generic constraints, mapped types, and intersection types ([Bornea, "Approximating Row Type Polymorphism in TypeScript"](https://medium.com/@gabriel-bornea/approximating-row-type-polymorphism-in-typescript-495ebd5a623d)). Go makes a narrower structural choice: a type satisfies an interface simply by having the required methods, "no keywords, declarations, or ceremony required" ([Go specification](https://go.dev/ref/spec#Interface_types)), though Go's structs are otherwise nominal. Scala 3 offers opt-in structural types through the `Selectable` trait and refinement syntax, but does not infer them — you write the structure down ([Scala 3 Reference](https://docs.scala-lang.org/scala3/reference/changed-features/structural-types.html)). Rust sits firmly at the nominal end: its structs and enums are nominal, it "notably lacks a built-in, structural type option," and row polymorphism "is notably absent from Rust's struct system" ([Andreas, "Structural vs Nominal Typing in Rust"](https://felixandreas.me/blog/nominal-vs-structural-types/)). That absence is the ground CGP works on. - -### Row polymorphism proper, in PureScript - -Row polymorphism is structural flexibility delivered by a *row variable* rather than a subtype relation. Where structural subtyping coerces a wider value to a narrower type, row polymorphism quantifies over "the rest of the fields," so a function accepts any record that has *at least* the fields it names while a type variable carries the others through unchanged ([Wikipedia: Row polymorphism](https://en.wikipedia.org/wiki/Row_polymorphism)). PureScript is the clearest working example, because rows are the foundation of its record system rather than a library bolted on. - -In PureScript a *row* and a *record* are different things. A row is "an unordered collection of named types, with duplicates," of kind `Row k` — not itself a value ([PureScript language reference](https://github.com/purescript/documentation/blob/master/language/Types.md)) — and a record is a row wrapped by the `Record` constructor, `Record :: Row Type -> Type`, for which the brace form `{ name :: String }` is sugar for `Record ( name :: String )` ([Pursuit: Prim](https://pursuit.purescript.org/builtins/docs/Prim)). An *open* row adds a tail variable after a pipe, which is where the polymorphism lives: - -```purescript --- accepts any record that has at least firstName and lastName; --- `r` captures whatever other fields the caller's record carries -fullName :: forall r. { firstName :: String, lastName :: String | r } -> String -fullName p = p.firstName <> " " <> p.lastName - -fullName { firstName: "Ada", lastName: "Lovelace" } -- ok -fullName { firstName: "Ada", lastName: "Lovelace", age: 36 } -- also ok; age flows through `r` -``` - -The record operations that make rows useful are governed by the `Prim.Row` type classes, which are relations over rows resolved during type checking. `Cons` "asserts that one row of types can be obtained from another by inserting a new label/type pair"; `Union` computes "the union of two rows of types (left-biased, including duplicates)"; `Nub` removes "duplicate labels from rows"; and `Lacks` "asserts that a label does not occur in a given row" ([Pursuit: Prim.Row](https://pursuit.purescript.org/builtins/docs/Prim.Row)). On top of these sit the everyday operations — field access `p.field`, functional update `p { field = newValue }`, and record merge — and libraries such as `purescript-record` and `purescript-simple-json`, the latter decoding JSON straight into a record type with no hand-written codec by reflecting over the row at the type level ([purescript-simple-json](https://github.com/justinwoo/purescript-simple-json)). - -PureScript's specific design — rows that permit *duplicate* labels with scoping — is Daan Leijen's, and the lineage is worth naming because it recurs across the field. Row variables originate with Mitchell Wand's "Complete Type Inference for Simple Objects" (LICS 1987), which introduced row variables but assumed labels were unique ([Wand 1987](https://www.ccs.neu.edu/home/wand/papers/wand-lics-87.pdf)); Didier Rémy made rows work cleanly inside ML with principal types (POPL 1989) ([Rémy 1989](https://dl.acm.org/doi/10.1145/75277.75284)); and Leijen's "Extensible records with scoped labels" (TFP 2005) dropped the uniqueness restriction, allowing duplicate labels and "effectively introducing a form of scoping over the labels" ([Leijen 2005](https://www.microsoft.com/en-us/research/publication/extensible-records-with-scoped-labels/)). PureScript adopts this directly: it "allows duplicate labels in rows, and the meaning of `Record r` is based on the first occurrence of each label," and its creator Phil Freeman cites Leijen's paper and prototyped the design in a repository named `purescript-scoped-labels` ([Pursuit: Prim](https://pursuit.purescript.org/builtins/docs/Prim); [Freeman, PureScript type-system talk](https://speakerdeck.com/paf31/an-overview-of-the-purescript-type-system)). - -### Rows as a kind versus rows as predicates - -Two designs realize row polymorphism, and the difference decides how naturally it ports to a language like Rust. PureScript, following Wand, Rémy, and Leijen, makes rows a *built-in kind* — `Row k` is a sort of type the compiler knows, with its own unification — so record and variant types are wrapped rows and the row variable is a first-class type-level entity. The alternative, due to Benedict Gaster and Mark Jones, encodes rows through *qualified types* instead: a function is polymorphic over an ordinary type variable, and the constraints that variable must satisfy — a *lacks* predicate `r\l` ("row `r` has no label `l`") and its dual *has* predicate — are discharged by the same constraint-resolution machinery that resolves type-class instances, with no new kind ([Gaster & Jones, *A Polymorphic Type System for Extensible Records and Variants*](http://web.cecs.pdx.edu/~mpj/pubs/polyrec.html)). Gaster and Jones designed their system precisely so it could be "understood and implemented as a natural extension" of Haskell and ML, because it reuses the qualified-types machinery already present for type classes. - -This second design is the one CGP most closely resembles, and naming it corrects the reflex to say "CGP has no rows." CGP has no row *kind* and no row *variable*, but it does have row *predicates*: a `HasField` bound is a has-predicate over a context's row, resolved by the trait solver exactly as Gaster and Jones resolve their constraints by qualified-type inference. Where PureScript threads a row variable, CGP threads a *context* type variable and constrains it — the predicate-based rows of Gaster–Jones, carried by Rust's traits rather than a bespoke row kind. A further refinement of the kind-based systems, Rémy's *presence polymorphism*, encodes not merely which labels are present but a per-label *flag* recording presence or absence, so a function can be polymorphic in whether a given field is there at all ([Rémy 1989](https://dl.acm.org/doi/10.1145/75277.75284); [Row polymorphism, HandWiki](https://handwiki.org/wiki/Row_polymorphism)); CGP reproduces exactly this with the per-field presence markers shown later. - -### Extensible variants: the sum-side dual - -Everything above concerns records — products of named fields — and it has an exact dual for enums, the *sums* of named variants. Where a row-polymorphic record function accepts "at least these fields," a row-polymorphic variant value promises "at most these cases," and the same row machinery types both. OCaml is the canonical industrial home of this dual through its *polymorphic variants*, which need no central type declaration and whose tags are written with a backtick. Their types carry bounds in either direction: an open type `` [> `On ] `` "accepts at least the specified tags but may be extended," while a closed type `` [< `On | `Off ] `` restricts to the listed ones ([OCaml manual](https://ocaml.org/manual/5.4/polyvariant.html)): - -```ocaml -(* no type declaration needed; the variant type is inferred and reusable *) -let to_int = function - | `On -> 1 - | `Off -> 0 - | `Number n -> n -(* inferred: [< `Number of int | `Off | `On ] -> int *) -``` - -Extensible variants and records are the classic answer to the **expression problem**, Philip Wadler's name for the challenge of adding both new cases to a datatype *and* new operations over it "without recompiling existing code, and while retaining static type safety" ([Wadler 1998](https://homepages.inf.ed.ac.uk/wadler/papers/expression/expression.txt)). Wadler's own framing is a table: "cases as rows and functions as columns" — functional languages fix the rows and add columns easily, object-oriented languages fix the columns and add rows easily, and extensible data types let you add both. That table is why row-typed records and variants matter beyond convenience, and it is the same problem CGP's [extensible variants](../concepts/extensible-variants.md) set out to solve. - -Haskell realizes the constraint-based route for the sum side directly in Wouter Swierstra's *Data types à la carte*, the canonical functional answer to the expression problem and the closest cousin to CGP's variant machinery. It represents an extensible datatype as a *coproduct of signature functors* — `f :+: g`, the type-constructor analogue of `Either` — together with a type-class constraint `f :<: g` ("`f` is a sub-signature of `g`") whose evidence supplies an injection `inj :: f a -> g a` and a partial projection `prj :: g a -> Maybe (f a)` ([Swierstra, *Data types à la carte*](https://www.cs.tufts.edu/~nr/cs257/archive/wouter-swierstra/DataTypesALaCarte.pdf)). New cases and new operations both slot in without editing existing code, and — the point for this comparison — the whole construction is built from *constraints resolved like type classes* rather than a built-in row kind: the Gaster–Jones route applied to sums, with `inj`/`prj` doing the injection and projection that a row system's containment predicate governs. - -### The unifying theory: "rows by any other name" - -Beneath the language-specific designs lies a general theory, and the research paper this document studies — J. Garrett Morris and James McKinna's "Abstracting extensible data types: or, rows by any other name" (POPL 2019) — is its most complete statement. The paper presents "a novel typed language for extensible data types, generalizing and abstracting existing systems of row types and row polymorphism," introducing **row theories**, "a monoidal generalization of row types, giving a general account of record concatenation and projection (dually, variant injection and branching)" ([Morris & McKinna 2019, abstract](https://researchportal.hw.ac.uk/en/publications/abstracting-extensible-data-types-or-rows-by-any-other-name/); [ACM DOI 10.1145/3290325](https://dl.acm.org/doi/10.1145/3290325)). The value of the abstraction is that Wand's, Rémy's, and Leijen's differing systems become *instances* of one framework rather than rival designs. - -The framework rests on two predicates over rows, and their shapes map so cleanly onto CGP that they are worth stating precisely. The **combination** predicate `ρ₁ ⊙ ρ₂ ~ ρ₃` says that rows `ρ₁` and `ρ₂` combine to yield `ρ₃`, and the **containment** predicate `ρ₁ ≲ ρ₂` says one row is included in another ([Morris & McKinna, via the follow-up "Generic Programming with Extensible Data Types"](https://arxiv.org/pdf/2307.08759)). Records and variants are then perfect duals over these predicates: a record is *built* by concatenation (combination) and *read* by projection (containment), while a variant is *built* by injection (containment) and *consumed* by branching (combination). The framework is parametric over the *row theory*, which fixes how rows combine — "simple rows" forbid overlap and combine commutatively, "scoped rows" (Leijen's) are non-commutative and "particularly well suited to capturing algebraic effects" — and it maintains strong metatheory throughout, including coherence and principal types. Its deepest idea for the CGP comparison is that "evidence for type qualifiers has computational content, determining the implementation of record and variant operations": the proof that a row contains a label, or that two rows combine, is not a static checkmark but a runtime value that *is* the projection or concatenation code. The Haskell `row-types` library (`Data.Row`) is a direct implementation of this paper ([Hackage: Data.Row.Records](https://hackage.haskell.org/package/row-types-1.0.1.2/docs/Data-Row-Records.html)). - -### A third application: effect rows - -Rows type more than data, and their most prominent third use — *effects* — matters here because it is where row polymorphism most visibly both succeeds and fails. Koka types a computation's side effects as a *row* of effect labels, so `` in a signature means "may throw and diverge," applying the exact row polymorphism this document describes to effects instead of fields ([Leijen, *Koka: Programming with Row-Polymorphic Effect Types*](https://arxiv.org/pdf/1406.2061)). This is why Morris and McKinna single out *scoped* rows as "particularly well suited to capturing algebraic effects": an effect row, like a scoped record row, is an unordered collection of labels where duplicates and ordering carry meaning. The [algebraic effects](algebraic-effects.md) comparison develops the effect side in full; the point for rows is that one mechanism serves records, variants, *and* effects — and that its record and effect uses have diverged sharply in practice, since effect rows are precisely the ones languages have retreated from, as the next section shows. - -CGP's counterpart to an effect row is not a separate row kind but the same predicate set it uses for data: a provider's [impl-side dependencies](../concepts/impl-side-dependencies.md) are the capabilities it may use, the way an effect row is the effects a function may perform, and [`check_components!`](../reference/macros/check_components.md) discharges them the way a handler discharges an effect. Where the row languages keep a distinct row kind for each of records, variants, and effects, CGP collapses all three onto trait predicates over a context — a point the [algebraic effects](algebraic-effects.md) document takes up from the effect side. - -### Two cautionary tales: PureScript effects and Gleam records - -Two languages that once had prominent row systems removed them, and the reasons are directly relevant to CGP's positioning. PureScript's original effect monad, `Eff`, tracked side effects in a *row* of effect types; version 0.12 replaced it with a monomorphic `Effect` that drops the effect row, because "getting the effect rows to line up was sometimes quite tricky, without providing a great deal of benefit," the rows "were not sufficiently useful in terms of actually guaranteeing what effects were run," and — the recurring theme — they "led to many problems with users not knowing how to solve type errors with unification of effect rows" ([PureScript-Resources, "Why did PureScript go from Eff to Effect?"](https://purescript-resources.readthedocs.io/en/latest/eff-to-effect.html)). PureScript kept row-typed *records*, but retreated from row-typed *effects*. - -Gleam went further and removed row-typed records outright. Early Gleam typed records as Erlang maps with genuine row polymorphism, but version 0.4 removed them, making the type system "simpler, less structural and more nominal in style" ([Gleam v0.4 release notes](https://gleam.run/news/gleam-v0.4-released/)). Current Gleam is nominal: its creator states "all values have to belong to a type" ([GitHub discussion #3223](https://github.com/gleam-lang/gleam/discussions/3223)), and what resembles row polymorphism today — the `.field` accessor across variants and the "variant inference" added in v1.6.0 — is flow-sensitive local narrowing that "does not persist across function boundaries," not row polymorphism at all ([welltypedwitch, "Nominal for storing, structural for manipulating"](https://welltypedwitch.bearblog.dev/nominal-for-storing-structural-for-manipulating/)). Both retreats were driven substantially by the cost of row-unification error messages — a cost any comparison to CGP must take seriously, because CGP's type-level machinery is subject to the same failure mode. - -## How CGP expresses it - -CGP has no row kind, no anonymous record type, and no row variable; it keeps Rust's nominal structs and enums and *derives* a type-level description of a type's shape, then matches field and variant names through the trait system. The result is the same field-and-variant-driven extensibility a row system provides, reached by the predicate-based route rather than the kind-based one: a type's shape is a type-level list, and the row predicates become trait constraints resolved by the trait solver — the Gaster–Jones qualified-types style [described above](#rows-as-a-kind-versus-rows-as-predicates), carried by Rust's traits. The two halves are deliberately dual, mirroring the record/variant duality of row theories. - -### A struct is a closed row; `HasField` is row containment - -The [`#[derive(HasFields)]`](../reference/traits/has_fields.md) derive gives a struct the type-level equivalent of a closed row — a [`Product!`](../reference/macros/product.md) of [`Field`](../reference/types/field.md) entries, each tagged by a [`Symbol!`](../reference/macros/symbol.md) type-level string, one per field: - -```rust -#[derive(HasField, HasFields)] -pub struct Person { - pub first_name: String, - pub last_name: String, -} - -// generated: -// type Fields = Product![ -// Field, -// Field, -// ]; -``` - -Where PureScript's `fullName` names its required fields with an open row `{ firstName :: String | r }`, a CGP provider names them as [impl-side dependencies](../concepts/impl-side-dependencies.md): a `Context: HasField` bound, usually written as an [`#[implicit]`](../reference/attributes/implicit.md) argument or a getter. That bound is exactly the containment predicate `ρ₁ ≲ ρ₂` from the row-theory framework — "this row contains this label of this type" — and any context whose derived row includes the field satisfies it, with the context's other fields playing the role of the tail variable `r`. The difference from PureScript is that the row variable is implicit and never named: CGP does not infer or carry a residual row, it simply resolves the containment bound against whatever concrete context is wired. - -### Building a record is row combination - -Assembling a struct field by field is CGP's record concatenation, the combination predicate `ρ₁ ⊙ ρ₂ ~ ρ₃`. The [builder family](../reference/traits/has_builder.md) walks a *partial record* from empty to complete, and [`CanBuildFrom`](../reference/traits/cast.md) absorbs the shared fields of one struct into another's builder in a single step — the direct analogue of PureScript's `Record.union` and the paper's record concatenation: - -```rust -let combined: FooBarBaz = FooBarBaz::builder() - .build_from(FooBar { foo: 1, bar: "bar".into() }) // splice one row in - .build_from(Baz { baz: true }) // splice another - .finalize_build(); // exists only when the row is complete -``` - -The underlying type-level operations are literally a row algebra: [`ConcatProduct`](../reference/traits/product_ops.md) splices two products, `AppendProduct` adds one field, and `MapFields` rewrites every entry — pure functions from type lists to type lists that the compiler evaluates during type checking at no runtime cost. That `finalize_build` type-checks only when every field is present is the compile-time completeness guarantee a closed row gives, recovered for generic Rust: the [application builder](../examples/application-builder.md) example assembles a whole application context this way, with independent providers each contributing one subsystem's fields. - -### Presence markers are presence polymorphism - -CGP tracks field presence with the same per-field flags Rémy's presence polymorphism introduced, made concrete as [`MapType`](../reference/traits/map_type.md) markers on a partial record. The companion type a builder walks carries one marker per field: `IsPresent` stores the value, `IsNothing` stores `()`, and the [optional-and-defaulted-field extensions](../reference/traits/optional_fields.md) add `IsOptional` for a field that may or may not be there; the variant side uses `IsVoid` to mark a case ruled out during extraction. A builder starts at all-`IsNothing`, each `build_field` flips one marker to `IsPresent`, and [`FinalizeBuild`](../reference/traits/has_builder.md) exists only at the all-`IsPresent` configuration, so an incomplete record cannot be finalized. These markers are presence flags in Rémy's exact sense — a per-label record of whether a field is there — lifted to Rust's type level, and the optional-field transforms that rewrite every field's marker at once are presence polymorphism made operational. Where a presence-polymorphic function abstracts over whether a field is present, a CGP builder tracks that presence through the markers and refuses to finalize until it is resolved. - -### An enum is a row-typed sum; upcast and downcast are injection and branching - -The variant side is the exact dual. [`#[derive(HasFields)]`](../reference/traits/has_fields.md) represents an enum as a [`Sum!`](../reference/macros/sum.md) of `Field` entries over the `Either`/`Void` spine, and the [structural casts](../reference/traits/cast.md) implement the paper's variant operations directly. `CanUpcast` lifts a value of a narrow enum into a wider one whose variants are a superset — variant *injection*, always successful because every source case has a home in the target — and `CanDowncast` narrows the other way, the containment-guarded projection: - -```rust -// upcast: variant injection — always succeeds -let wide = FooBar::Foo(1).upcast(PhantomData::); -assert_eq!(wide, FooBarBaz::Foo(1)); - -// downcast: succeeds only for variants the target still has -FooBarBaz::Baz(true).downcast(PhantomData::).ok(); // None — Baz has no home in FooBar -``` - -Branching — the combination predicate on the sum side — is CGP's [extensible visitor](../concepts/extensible-variants.md): the `MatchWithValueHandlers` dispatcher derives one extract-and-handle step per variant from the enum's row and runs them as a pipeline, and because each failed extraction rules out a variant at the type level (marking it the uninhabited `Void`), the final match is provably exhaustive with no wildcard arm. Add a variant without a handler and the code stops compiling — the same static safety OCaml's polymorphic variants give, and CGP's answer to the expression problem the [expression interpreter](../examples/expression-interpreter.md) and [extensible shapes](../examples/extensible-shapes.md) examples work through end to end. In *Data types à la carte* terms, `CanUpcast` is `inj` and `CanDowncast` is `prj` — but where à la carte's `prj` returns a `Maybe` with no exhaustiveness check, CGP's branching consumes the whole sum and proves it covered every case, so the projection that à la carte leaves partial CGP makes total. - -### Providers are the paper's computational evidence - -The one point where CGP lines up with the *theory* more than with any implementing language is Morris and McKinna's insight that a row predicate's evidence "has computational content." In CGP that evidence is concrete and visible: the `HasField` impl a derive generates *is* the projection code, the `CanBuildFrom` recursion *is* the concatenation code, and a wired provider *is* the runtime witness that a context can perform an operation. Where a row-typed language elaborates the evidence invisibly during compilation, CGP surfaces it as ordinary traits and impls a programmer can read — which is the same trade the rest of CGP makes, [bypassing coherence](../concepts/coherence.md) by making the machinery explicit rather than magical. - -## What users like and dislike - -Row polymorphism and extensible data types are loved for the extensibility and reuse they unlock, and the praise is consistent across languages. PureScript users value that a single row-polymorphic function serves many record shapes, which "can greatly reduce refactoring" and lets one "create functions that apply to many different kinds of structures without needing to specify every structure" ([Fowler, "Row Polymorphism without the Jargon"](https://jadon.io/blog/row-polymorphism/)), and that rows drive boilerplate-free JSON decoding and type-safe framework bindings ([Nguyen, "Record Row Type and Row Polymorphism"](https://hgiasac.github.io/posts/2018-11-18-Record-Row-Type-and-Row-Polymorphism.html)). One community rule of thumb captures the sweet spot well: "an extensible record is 'better' than a non-extensible record as a function argument," and "an extensible variant is 'better' than a non-extensible variant as a function result" ([PureScript Discourse](https://discourse.purescript.org/t/when-to-use-extensible-types-when-modeling-a-domain/217)). OCaml's manual makes the parallel case for polymorphic variants: extensibility and inferred, reusable variant types with no central declaration. - -The dislikes cluster around three recurring pains, and CGP shares the first two. The loudest is **error messages**: row unification produces enormous, hard-to-read diagnostics — one PureScript user reported row-unification errors "around 152Kb" that "clog up the terminal," and another found the verbose output "mostly intimidates me" and unhelpful for a simple field typo ([PureScript Discourse](https://discourse.purescript.org/t/upcoming-changes-to-error-messages-for-large-records-rows/3696)). This is the same pain that drove PureScript off effect rows and Gleam off row-typed records. The second is **complexity and cognitive burden**: extensible records are widely seen as a specialist tool — "unless you are developing libraries, or generics and type-level programming, you don't need them" ([Nguyen](https://hgiasac.github.io/posts/2018-11-18-Record-Row-Type-and-Row-Polymorphism.html)) — and one user who modeled a domain with them "kinda regret[ted] it, wishing instead I'd just written separate functions, one for each nominal type" ([PureScript Discourse](https://discourse.purescript.org/t/when-to-use-extensible-types-when-modeling-a-domain/217)). The third is more specific to *pure* structural typing and is one CGP largely avoids: a purely structural function can be called on a semantically wrong value, so that "calling a function works but semantically it doesn't make sense, like finding the `area` of a `Fish`" ([Fowler](https://jadon.io/blog/row-polymorphism/)). OCaml's own manual echoes the safety concern, warning that polymorphic variants "result in a weaker type discipline" and undetected tag typos, recommending core variants "for simple programs" ([OCaml manual](https://ocaml.org/manual/5.4/polyvariant.html)). - -## How CGP compares - -CGP makes a different structural bargain from a row-typed language, and the comparison turns on three axes. On **the type system**, CGP keeps Rust nominal and layers a structural view on top per type, opt-in via a derive, where PureScript, OCaml, and Elm make rows or structural variants a pervasive part of the language. That structural view is *predicate-based* rather than *kind-based* — the Gaster–Jones qualified-types tradition rather than PureScript's built-in `Row` kind — which is exactly why it grafts onto Rust's existing trait solver without any new type-system feature: a `HasField` bound is a row constraint, and constraint resolution is what traits already do. This is also CGP's largest advantage over pure structural typing: because a provider still binds to a *context* it was wired for, the "area of a `Fish`" hazard mostly does not arise — a provider reads a context's fields structurally, but which providers a context has is a nominal, wired decision, so structure grants access without erasing identity. On **inference**, CGP is the weaker tool: a row system infers `{ firstName :: String | r }` and unifies rows automatically, while CGP asks you to derive the shape and name the wiring, with no residual row inferred or carried. On **evidence**, CGP is unusually explicit — the projection, concatenation, and injection code are ordinary traits and impls, not compiler-internal elaboration — which aids reading and debugging at the cost of verbosity. - -The costs are real and should be stated plainly. CGP inherits the very pain that pushed two languages away from rows: a mis-wired or incomplete structural operation surfaces as a long, generated-type-heavy trait error, the CGP-idiom echo of a 152Kb row-unification message. CGP's mitigation is [`check_components!`](../reference/macros/check_components.md), which forces the missing field or variant to be named at the wiring site rather than deep inside a use, but the diagnostics remain heavier than a nominal `match`. CGP also demands more ceremony than an anonymous record: derives, type-level tags, and wiring where a row system would infer everything. Where a program genuinely wants terse anonymous records with full inference, and can pay the error-message cost, a real row system such as PureScript's is the better tool, and reaching for CGP's machinery to emulate it would be over-engineering. Where a program is already in nominal Rust and wants structural field-and-variant access at a few well-chosen points — a builder assembled from independent parts, a visitor over an open set of variants, a cast between sibling enums — CGP delivers row polymorphism's payoff without adopting a new kind system, and pays the complexity only where it is used. - -## Presenting CGP to someone who knows this - -A reader fluent in rows arrives with almost the whole mental model, and the fastest way in is to map the vocabulary directly. A struct's derived `HasFields` shape **is a closed row**; [`Product!`](../reference/macros/product.md) and [`Sum!`](../reference/macros/sum.md) are the **row spines** for records and variants; a `HasField` bound is **row containment** (`ρ₁ ≲ ρ₂`); [`ConcatProduct`](../reference/traits/product_ops.md) and `CanBuildFrom` are **record concatenation** (`ρ₁ ⊙ ρ₂ ~ ρ₃`); `CanUpcast`/`CanDowncast` are **variant injection and branching**; and the extensible visitor is the **expression-problem solution** they already know from polymorphic variants. For a reader who knows the Morris–McKinna paper specifically, the sharpest framing is that CGP is a row system whose *evidence* is first-class and readable: the providers and impls are the computational content the paper says row predicates carry, made into ordinary code. For a reader steeped in Haskell's qualified-types tradition — Gaster–Jones records, *Data types à la carte*, or the `row-types` library — the framing is even more direct: a `HasField` bound is a *has*-predicate, `CanUpcast`/`CanDowncast` are `inj`/`prj`, and CGP is predicate-based rows resolved by the trait solver, which is the mechanism they already reach for; the only shift is that Rust's traits play the role of Haskell's class constraints. - -Two expectations must be corrected up front, because a row-trained reader will assume both and be misled. The first is **inference**. This reader expects CGP to infer `{ name :: String | r }` and unify rows for them, and it does not — CGP has no row variable and no row inference; a provider names its required fields as bounds, and a context's shape comes from a derive, not from unification. Present that not as a missing feature but as the deliberate consequence of staying nominal: because CGP does not unify open rows, it also does not produce the row-unification error messages that drove PureScript off effect rows and Gleam off row-typed records — the failure mode they most resent is designed out of the pervasive case, though it reappears, in CGP's own idiom, as verbose trait errors that [`check_components!`](../reference/macros/check_components.md) localizes. The second is **nominality**. This reader may expect structural typing to mean "any shape-compatible value works anywhere," and CGP deliberately does not: structure grants a provider access to a context's fields, but a context still nominally chooses its providers by wiring, so the "area of a `Fish`" call their own tools permit is not automatically expressible. Frame that as the best of both — structural access where you want reuse, nominal identity where you want safety — and lead with the pitch that lands hardest for this audience: row polymorphism's extensibility, brought to a nominal systems language that never had a row kind, opt-in per type so the complexity and the error-message tax are paid only where the power is used. - -## Sources - -The account of the related work draws on official language documentation, the primary research literature on row types, and cited community writing for sentiment; the CGP snippets are drawn from the knowledge base's [extensible records](../concepts/extensible-records.md), [extensible variants](../concepts/extensible-variants.md), and [casting](../reference/traits/cast.md) documents and verified against current macro behavior. - -- [Wikipedia — Structural type system](https://en.wikipedia.org/wiki/Structural_type_system) and [Nominal type system](https://en.wikipedia.org/wiki/Nominal_type_system) — definitions of structural and nominal typing and their contrast. -- [Wikipedia — Row polymorphism](https://en.wikipedia.org/wiki/Row_polymorphism) — row polymorphism as parametric, row-variable-based structural polymorphism, distinct from structural subtyping, and its absence in Rust. -- [PureScript language reference — Types](https://github.com/purescript/documentation/blob/master/language/Types.md) and [Pursuit — Prim](https://pursuit.purescript.org/builtins/docs/Prim) — rows as unordered labeled collections of kind `Row k`, the `Record` constructor and brace sugar, open-row syntax, and duplicate-label (first-occurrence) semantics. -- [Pursuit — Prim.Row](https://pursuit.purescript.org/builtins/docs/Prim.Row) — the `Cons`, `Union`, `Nub`, and `Lacks` row type classes and what each constrains. -- [purescript-simple-json](https://github.com/justinwoo/purescript-simple-json) — row-reflection-driven, codec-free JSON decoding as a representative row-types application. -- [Wand, "Complete Type Inference for Simple Objects" (LICS 1987)](https://www.ccs.neu.edu/home/wand/papers/wand-lics-87.pdf), [Rémy, "Type checking records and variants…" (POPL 1989)](https://dl.acm.org/doi/10.1145/75277.75284), and [Leijen, "Extensible records with scoped labels" (TFP 2005)](https://www.microsoft.com/en-us/research/publication/extensible-records-with-scoped-labels/) — the row-polymorphism lineage, with Leijen's scoped-labels design the one PureScript adopts, and Rémy's the origin of the presence/absence field flags that CGP's `MapType` markers reproduce. -- [Gaster & Jones, "A Polymorphic Type System for Extensible Records and Variants" (Nottingham NOTTCS-TR-96-3, 1996)](http://web.cecs.pdx.edu/~mpj/pubs/polyrec.html) and [Row polymorphism (HandWiki)](https://handwiki.org/wiki/Row_polymorphism) — the qualified-types / lacks-predicate design of rows, resolved like type-class constraints rather than through a built-in row kind, and the presence/absence flag account; this is the design CGP's `HasField`-bound rows most closely resemble. -- [Swierstra, "Data types à la carte" (JFP 2008)](https://www.cs.tufts.edu/~nr/cs257/archive/wouter-swierstra/DataTypesALaCarte.pdf) ([Wadler's summary](https://wadler.blogspot.com/2008/02/data-types-la-carte.html)) — coproducts of signature functors (`:+:`), the `:<:` sub-signature constraint, and its `inj`/`prj` injection and projection, the constraint-based Haskell solution to the expression problem that mirrors CGP's `Sum!` and `CanUpcast`/`CanDowncast`. -- [Freeman, "An Overview of the PureScript Type System"](https://speakerdeck.com/paf31/an-overview-of-the-purescript-type-system) — the explicit link from PureScript's rows to Leijen's scoped labels. -- [Morris & McKinna, "Abstracting extensible data types: or, rows by any other name" (POPL 2019)](https://researchportal.hw.ac.uk/en/publications/abstracting-extensible-data-types-or-rows-by-any-other-name/) ([ACM DOI 10.1145/3290325](https://dl.acm.org/doi/10.1145/3290325)) and the follow-up [Morris & McKinna, "Generic Programming with Extensible Data Types" (arXiv 2307.08759)](https://arxiv.org/pdf/2307.08759) — row theories as a monoidal generalization, the combination (`⊙`) and containment (`≲`) predicates, record/variant duality, coherence and principal types, and evidence with computational content. -- [Hackage — Data.Row.Records (row-types)](https://hackage.haskell.org/package/row-types-1.0.1.2/docs/Data-Row-Records.html) — the Haskell library implementing the Morris–McKinna framework. -- [Leijen, "Koka: Programming with Row-Polymorphic Effect Types" (MSFP 2014)](https://arxiv.org/pdf/1406.2061) — the application of row polymorphism to *effects*, the third row use alongside records and variants, and the setting where scoped rows capture algebraic effects; developed against CGP in the [algebraic effects](algebraic-effects.md) comparison. -- [OCaml manual — Polymorphic variants](https://ocaml.org/manual/5.4/polyvariant.html) and [Types](https://ocaml.org/manual/5.4/types.html) — polymorphic variants and object row typing, their open/closed bounds, and the documented weaker type discipline. -- [Wadler, "The Expression Problem" (1998)](https://homepages.inf.ed.ac.uk/wadler/papers/expression/expression.txt) — the expression problem and its rows-and-columns framing. -- [TypeScript Handbook — structural typing](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-oop.html) and [Bornea, "Approximating Row Type Polymorphism in TypeScript"](https://medium.com/@gabriel-bornea/approximating-row-type-polymorphism-in-typescript-495ebd5a623d) — structural (duck) typing and its distinction from true row polymorphism. -- [Go specification — Interface types](https://go.dev/ref/spec#Interface_types), [Scala 3 Reference — Structural Types](https://docs.scala-lang.org/scala3/reference/changed-features/structural-types.html), [Elm — Records](https://elm-lang.org/docs/records), and [Andreas, "Structural vs Nominal Typing in Rust"](https://felixandreas.me/blog/nominal-vs-structural-types/) — the wider structural-typing landscape and Rust's nominal baseline. -- [PureScript-Resources, "Why did PureScript go from Eff to Effect?"](https://purescript-resources.readthedocs.io/en/latest/eff-to-effect.html) — the removal of row-typed effects and the role of row-unification errors. -- [Gleam v0.4 release notes](https://gleam.run/news/gleam-v0.4-released/), [GitHub discussion #3223](https://github.com/gleam-lang/gleam/discussions/3223), and [welltypedwitch, "Nominal for storing, structural for manipulating"](https://welltypedwitch.bearblog.dev/nominal-for-storing-structural-for-manipulating/) — Gleam's removal of row-typed records, its nominal stance, and why variant inference is not row polymorphism. -- [Fowler, "Row Polymorphism without the Jargon"](https://jadon.io/blog/row-polymorphism/), [Nguyen, "Record Row Type and Row Polymorphism"](https://hgiasac.github.io/posts/2018-11-18-Record-Row-Type-and-Row-Polymorphism.html), and PureScript Discourse threads on [error messages](https://discourse.purescript.org/t/upcoming-changes-to-error-messages-for-large-records-rows/3696) and [when to use extensible types](https://discourse.purescript.org/t/when-to-use-extensible-types-when-modeling-a-domain/217) — cited community sentiment on what users like and dislike. diff --git a/docs/related-work/type-classes.md b/docs/related-work/type-classes.md deleted file mode 100644 index ca24c44b..00000000 --- a/docs/related-work/type-classes.md +++ /dev/null @@ -1,173 +0,0 @@ -# Type classes - -Type classes are the mechanism for *principled ad-hoc polymorphism* — a class declares an interface, an instance implements it for a type, and the compiler resolves which instance a constrained function needs by translating the class into a hidden *dictionary* argument. Rust traits are Rust's type classes, so CGP already lives inside a type-class system; its defining move is to escape that system's *coherence* — the one-instance-per-type rule — by making instances first-class values selected explicitly per context, which is exactly the freedom that overlapping and incoherent instances chase in Haskell, Agda, and Lean without ever making it safe. - -## Purpose - -Type classes solve overloading without giving up type inference or static safety. Before them, a name like `show` or `+` either meant one fixed thing or was resolved by unprincipled special-casing in the compiler; type classes, introduced by Philip Wadler and Stephen Blott to "make ad-hoc polymorphism less ad hoc," let a single name be overloaded across many types in a way the type system tracks and the programmer extends ([Wadler & Blott, *How to make ad-hoc polymorphism less ad hoc*](https://dl.acm.org/doi/10.1145/75277.75283)). A function constrained by `Show a =>` works for every type with a `Show` instance, the right instance is found by the compiler, and no instance is chosen by accident. - -This is the comparison closest to CGP's core, because CGP is built on Rust traits and Rust traits *are* type classes. Where the [dependency-injection](dependency-injection.md) and [implicit-parameters](implicit-parameters.md) documents approach CGP from the runtime-container and implicit-value angles, this one meets it on its own ground: a CGP [component](../reference/macros/cgp_component.md) is a class, a [provider](../reference/macros/cgp_impl.md) is an instance, [wiring](../reference/macros/delegate_components.md) is instance resolution, and [impl-side dependencies](../concepts/impl-side-dependencies.md) are class constraints. The single thing CGP changes is coherence, and the whole knowledge base's account of [bypassing coherence](../concepts/coherence.md) is, read against this document, an account of how to have the overlapping and incoherent instances every type-class language struggles to offer safely. - -## The concept in depth - -Type classes have a stable core — classes, instances, constraints, and the dictionary-passing translation — and a large periphery of coherence rules and the extensions that relax them, realized differently across Haskell, Agda, and Lean. The subsections build from the core translation through the coherence property that governs it, the overlapping and incoherent extensions that bend it, the two dependently-typed treatments that abandon it, and the *modular type classes* result that reframes the whole thing in terms of ML modules. - -### Classes, instances, and dictionary passing - -A class declares an interface, an instance implements it for a type, and a constraint requires it — and underneath, the class is a record of functions passed invisibly. In Haskell a class and an instance read as: - -```haskell -class Show a where - show :: a -> String - -instance Show Bool where - show True = "True" - show False = "False" - -describe :: Show a => a -> String -describe x = "value: " ++ show x -``` - -The `Show a =>` constraint on `describe` is the crux. Wadler and Blott's translation compiles a class into a *dictionary* — a record whose fields are the class methods — an instance into a dictionary value, and a constrained function into one that takes the dictionary as an extra hidden argument, so `describe` elaborates to `describe dict x = "value: " ++ show dict x` and the compiler supplies the `Show Bool` dictionary at each call ([Wadler & Blott 1989](https://dl.acm.org/doi/10.1145/75277.75283)). Dictionary passing is the shared implementation model behind every system in this document, and behind CGP: it is the same idea as the evidence passing in the [algebraic effects](algebraic-effects.md) comparison and the qualified-types constraints in the [row polymorphism](row-polymorphism.md) one. Classes also compose through *superclasses* — `class Eq a => Ord a` requires every `Ord` type to be `Eq` — and modern class systems add *associated types* (a type member of a class), which correspond to CGP's [abstract-type components](../concepts/abstract-types.md). - -### Coherence: one instance per type, globally - -The property that governs instance resolution, and the one CGP discards, is *coherence*: for a given class and type there is one instance, and every resolution anywhere in the program finds the same one. Coherence is what makes silent, automatic resolution safe — because the compiler always finds the same `Show Bool`, it can insert the dictionary without the programmer worrying that a different `Show Bool` is chosen elsewhere. The property is usually decomposed into *confluence* (any way of resolving a constraint gives the same dictionary), *coherence* (the program behaves as if there were a canonical instance), and *global uniqueness* (there really is only one instance per type program-wide), which GHC guarantees for the instances in scope during a compilation but does not enforce across a whole program ([Yang, *Type classes: confluence, coherence and global uniqueness*](https://blog.ezyang.com/2014/07/type-classes-confluence-coherence-global-uniqueness/)). Two rules protect it: at most one instance per (class, type), and the *orphan rule* discouraging an instance defined in neither the class's nor the type's module. The canonical benefit is a `Set` of an ordered element type: because there is one `Ord` for that type, values inserted under one ordering and read under another can never disagree, so the set cannot silently corrupt. - -Rust makes the same choice but *enforces* it where Haskell only advises. Rust traits are type classes with a hard orphan rule — an `impl Trait for Type` is allowed only when the crate owns the trait or the type — so coherence is a compile error to violate, not a discouraged convention ([Rust RFC 2451, *re-rebalancing coherence*](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html); [*Type class*, Wikipedia](https://en.wikipedia.org/wiki/Type_class)). This strictness is precisely the wall CGP is built to get around: the [coherence](../concepts/coherence.md) concept opens on exactly these overlap and orphan rules. - -### Overlapping instances - -The first extension relaxes the one-instance rule to allow instances where one is strictly more specific, letting the compiler pick the most specific match. Haskell exposes this through per-instance pragmas — `{-# OVERLAPPING #-}`, `{-# OVERLAPPABLE #-}`, `{-# OVERLAPS #-}` — that replaced the blunt module-wide `OverlappingInstances` flag in GHC 7.10, so that a general instance and a special case can coexist: - -```haskell -instance {-# OVERLAPPABLE #-} Show a => Show [a] where -- lists in general - show xs = "[" ++ intercalate "," (map show xs) ++ "]" - -instance {-# OVERLAPPING #-} Show [Char] where -- but strings specially - show s = s -``` - -Resolution commits to an instance only when it is strictly more specific than every other match; otherwise the program is rejected as ambiguous ([GHC User's Guide, *Instance declarations and resolution*](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/instances.html)). The cost is fragility: the most-specific heuristic is subtle, and — the point that matters for CGP — GHC's own manual warns that "overlapping instances must be used with care as they can give rise to incoherence (different instance choices are made in different parts of the program) even without `-XIncoherentInstances`." - -### Incoherent instances - -The second extension goes further and lets the compiler commit to an instance even when the choice is not unique, breaking coherence outright. An instance marked `{-# INCOHERENT #-}` may be selected in a context where a more specific instance could later apply, so different parts of a program can resolve the same constraint to different dictionaries. GHC documents the danger plainly: "GHC's optimiser assumes that type-classes are coherent, and hence it may replace any type-class dictionary argument with another dictionary of the same type," which "may cause unexpected results if incoherence occurs," and notes that `INCOHERENT` "still leads to indeterministic behavior and thus should be used with caution" ([GHC User's Guide](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/instances.html)). Incoherent instances are widely regarded as a last resort, because the very automation that makes type classes pleasant — the compiler silently choosing a dictionary — becomes a footgun once the choice is not guaranteed unique. This is the exact hazard CGP confronts head-on, and the reason its embrace of many instances has to be paired with explicit selection. - -### Instance arguments in Agda: coherence dropped, resolution scoped - -Agda offers type-class-style overloading without a class construct and without coherence, through *instance arguments*. Devriese and Piessens's design reuses Agda's ordinary dependently-typed records as classes and marks a resolved argument with double braces, so resolution is "a new type of function argument resolved from call-site scope in a type-directed way" ([Devriese & Piessens, *On the Bright Side of Type Classes: Instance Arguments in Agda*](https://dl.acm.org/doi/10.1145/2034574.2034796)): - -```agda -record Show (A : Set) : Set where - field show : A → String - -instance - showBool : Show Bool - showBool = record { show = λ b → if b then "true" else "false" } - -print : {A : Set} → {{Show A}} → A → String -print {{s}} x = Show.show s x -``` - -Agda does not enforce global uniqueness: instance search succeeds when exactly one instance in the call-site scope matches, and a genuine ambiguity is a *local* error at that use site rather than a program-wide coherence guarantee. This is a scoped, search-based resolution — closer to Scala's implicits than to Haskell's global coherence — and it is the first hint of the design CGP takes further: many instances allowed, the conflict resolved where the choice is made rather than by a global uniqueness rule. - -### Type classes in Lean: search, priorities, and the diamond problem - -Lean uses type classes pervasively for its mathematical library, resolves them by a backtracking tabled search with per-instance priorities, and pays for the absence of coherence with the *diamond problem*. A class is a structure, an instance is registered for search, and resolution finds a value of the required class: - -```lean -class Show (α : Type) where - show : α → String - -instance : Show Bool where - show b := if b then "true" else "false" - -#eval Show.show true -``` - -Because Lean allows multiple instances and orders them by priority, the same constraint can be satisfiable in more than one way, and when two resolution paths to the same class instance disagree — a *diamond*, "the existence of multiple conflicting terms of a class found within the typeclass instance graph" — inference can pick the wrong one or diverge; tabled resolution was introduced partly to tame the exponential blowup diamonds cause ([Selsam, Ullrich & de Moura, *Tabled Typeclass Resolution*](https://arxiv.org/pdf/2001.04301)). In a proof assistant the stakes are higher than a wrong value: two instances that are not *definitionally equal* break proofs that assume they coincide, so the Mathlib community must ensure that any overlapping instances are definitionally equal for every instantiation in the overlap — a standing, laborious discipline of incoherence management ([Baanen, *Use and abuse of instance parameters in the Lean mathematical library*](https://arxiv.org/pdf/2202.01629)). Lean is the clearest cautionary example of what unmanaged instance choice costs, and the sharpest backdrop for CGP's explicit per-context selection, which admits no diamond because a context names exactly one provider per component. - -### Modular type classes: classes as signatures, instances as structures - -The result that unifies this document with the [ML modules](ml-modules.md) comparison is that type classes *are* ML modules plus a resolution layer, established by Dreyer, Harper, and Chakravarty. Their *Modular Type Classes* treats **classes as signatures and instances as structures and functors**, adding a notion of designating certain instance modules as *canonical* within a scope so the compiler can resolve them implicitly, while keeping explicit module linking as the default ([Dreyer, Harper & Chakravarty, *Modular Type Classes*](https://people.mpi-sws.org/~dreyer/papers/mtc/main-long.pdf)). The reframing is illuminating because it separates two things a plain type-class system fuses: the *interface/implementation* structure (which is just modules) and the *canonical implicit resolution* (which is the extra ingredient type classes add on top). - -Separating them also names the tension every design in this document negotiates: **canonicity fights modularity.** Canonicity — one designated instance the compiler may assume everywhere — is what makes implicit resolution safe, but it is fundamentally a *non-modular* property, since a module cannot in general guarantee its instance is the canonical one program-wide, and full modular abstraction lets two modules each supply a different one. Dreyer, Harper, and Chakravarty confine canonicity to a scope to get some of both; OCaml's [modular implicits](ml-modules.md) inherit the same tension and, as their authors concede, cannot fully reconcile canonicity with modular abstraction. The design space is therefore a spectrum. At one end sits Haskell: fully implicit resolution, global coherence, one instance per type, no modularity of choice. In the middle sit modular type classes, modular implicits, Agda instance arguments, and Scala implicits: implicit resolution with canonicity scoped or dropped, paying in ambiguity and search subtleties. At the far end sits CGP: no canonicity at all, no search, explicit per-context selection — the fully modular extreme, where the price of dropping canonicity is that selection must be written down, and the reward is that overlapping and orphan instances become ordinary rather than exceptional. CGP is what the modular-type-classes line of work looks like when canonicity is abandoned entirely rather than merely scoped. - -## How CGP expresses it - -CGP is a type-class system with the coherence rule removed and replaced by explicit per-context instance selection, built by splitting each class into a consumer and a provider side. The consumer trait is an ordinary Rust type class; the provider trait and the wiring are the machinery that make instances first-class values, so many overlapping instances can coexist and a context picks one. Every construct below maps onto a type-class concept, and the extension features that type-class languages bolt on with pragmas are, in CGP, the default. - -### A component is a class; a provider is a first-class instance - -A CGP component is a type class, and a provider is an instance made into a named, selectable value. Declaring a component is declaring a class: - -```rust -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} -``` - -`CanCalculateArea` is the class interface, exactly as `class Show a` is. The difference is what an instance may be. A Haskell instance is anonymous and canonical — there is one `Show Bool`, chosen by the compiler — whereas a CGP provider is a named marker type carrying a provider-trait impl, which is precisely a *first-class dictionary*: a value you can name, pass, and choose among. Moving the implementation off the context and onto a provider marker, through the [consumer/provider split](../concepts/consumer-and-provider-traits.md), is the single device that lifts the one-instance-per-type limit — because a provider implements the provider trait for *its own* type, coherence never forbids a second one. Wiring then plays the role of resolution, but explicitly: a [`delegate_components!`](../reference/macros/delegate_components.md) entry names which instance a context uses, where Haskell's compiler would search for the canonical one. A provider's `where` bounds and [`#[uses]`](../reference/attributes/uses.md) imports are the class constraints threaded by dictionary passing, and the context is the dictionary that carries them. - -### Overlapping instances are the default, with no heuristic - -Where Haskell needs pragmas and a most-specific heuristic to permit overlap, CGP permits unlimited overlapping instances by construction and resolves them by naming, not guessing. The [modular serialization](../examples/modular-serialization.md) example defines several providers that all serialize the same types and overlap freely: - -```rust -#[cgp_impl(UseSerde)] -impl ValueSerializer -where - Value: serde::Serialize, -{ /* ... */ } - -#[cgp_impl(SerializeBytes)] -impl ValueSerializer -where - Value: AsRef<[u8]>, -{ /* ... */ } -``` - -A type like `String` matches both, so as vanilla type-class instances one would be rejected and even with `OVERLAPPING` the compiler would need one to be strictly more specific — but as CGP providers both compile, because each implements the provider trait for its own marker. There is no most-specific rule and therefore no ambiguity: a context simply names the provider it wants for a given type, `ValueSerializerComponent: SerializeBytes` or a per-type [`open`](../reference/macros/delegate_components.md) dispatch. The fragility GHC warns about — overlap silently producing incoherence — cannot arise, because the choice is never inferred. - -### Incoherent instances made deterministic and local - -The deepest correspondence is that CGP embraces the very incoherence type-class languages fear, and makes it safe by moving the choice from a global search to an explicit, per-context table. GHC warns that incoherent or overlapping instances let "different instance choices be made in different parts of the program," silently, and that the optimiser may swap one dictionary for another; Lean pays for the same freedom with the diamond problem. CGP allows the same many-instances-for-one-type situation — that is the whole point of [bypassing coherence](../concepts/coherence.md) — but the resolution is neither global nor implicit: each context selects one provider per component in its wiring, so two contexts may resolve the same type differently *on purpose*, and within a single context the choice is unambiguous and fixed. The `AppA` that serializes a `Vec` as hexadecimal and the `AppB` that serializes it as base64 are two coherent local scopes, not a global incoherence — the different-choice-in-different-places that is a footgun in Haskell is a feature in CGP because it is written down rather than inferred. CGP is, in one line, *incoherent instances with the incoherence made deterministic and the selection made explicit.* - -### No orphan rule, no newtype dance - -Two everyday type-class frustrations disappear because a provider's `Self` is always a type its crate owns. The orphan rule — Rust's enforced version of Haskell's convention — never bites, so a downstream crate can supply a provider for a component and a type it did not define, the exact case the [coherence](../concepts/coherence.md) concept shows type classes forbidding. And the newtype trick that Haskell forces when a type needs a second instance (a `Sum` and a `Product` monoid wrapping `Int`) is unnecessary: a second behavior for a type is just a second provider, named directly, with no wrapper type to introduce and unwrap. Where a type-class programmer restructures modules to place an instance or wraps a type to duplicate one, a CGP programmer writes another provider and a wiring line. - -## What users like and dislike - -Type classes are among the most loved features of the languages that have them, and the praise is specific. Programmers value that overloading is *principled* — the compiler tracks and checks it — and *inferred*, so the dictionary is supplied automatically and generic code reads as if monomorphic. They value coherence's payoff of *global uniqueness*: one `Ord` per type means a `Set` or a `Map` cannot be corrupted by mixing orderings, a guarantee that holds without the programmer thinking about it ([Yang 2014](https://blog.ezyang.com/2014/07/type-classes-confluence-coherence-global-uniqueness/)). And they value the *lawful abstractions* type classes make idiomatic — `Functor`, `Monoid`, `Monad` — and the way superclass constraints compose. In dependently-typed settings the same machinery organizes enormous algebraic hierarchies, which is why Lean's Mathlib rests on it. - -The complaints track the coherence rules and the extensions that strain against them. The one-instance-per-type limit forces the *newtype workaround*, which is boilerplate and "breaks down when the type is embedded in another type" ([Yang 2014](https://blog.ezyang.com/2014/07/type-classes-confluence-coherence-global-uniqueness/)); the *orphan rule* forces awkward module structure ([Queensland FP Lab, *Orphan Rules*](https://qfpl.io/posts/orphans-and-fundeps/)); *overlapping instances* are subtle and *incoherent instances* are, by GHC's own account, indeterministic and dangerous ([GHC User's Guide](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/instances.html)). Haskell has no easy *local* or *scoped* instance because local instances reintroduce a coherence problem, which is a long-running community sore point and a place where Scala, Agda, and Lean chose differently. Some practitioners argue the whole coherence bargain is wrong and prefer explicit dictionaries so the choice is visible and local, as Paul Chiusano does in *The trouble with typeclasses* ([Chiusano 2018](https://pchiusano.github.io/2018-02-13/typeclasses.html)); the coherence debate is active enough to have its own recent survey ([Racordon, *On the State of Coherence in the Land of Type Classes*](https://arxiv.org/pdf/2502.20546)). And in Agda and Lean the absence of enforced coherence surfaces as the *diamond problem* and the burden of keeping overlapping instances definitionally equal ([Baanen 2022](https://arxiv.org/pdf/2202.01629)), plus resolution-performance and error-message costs. - -## How CGP compares - -CGP makes the opposite coherence bargain from Haskell and a more disciplined one than Agda or Lean, and the trade is cleanest stated across three axes. On *resolution*, type classes are implicit and CGP is explicit: Haskell finds the dictionary, CGP asks a context to name the provider. On *coherence*, type classes are coherent — globally unique instances, enforced in Rust, conventional in Haskell — while CGP is deliberately incoherent at the definition level, hosting unlimited overlapping and orphan providers. On *safety of the incoherence*, CGP is the disciplined one: where Haskell's `INCOHERENT` and Lean's diamonds let a wrong instance be chosen silently or a proof break, CGP's per-context table makes every choice explicit and local, so incoherence never means indeterminism. Each side pays for what the other gets. Type classes get zero-boilerplate resolution and global uniqueness and pay with the newtype dance, the orphan rule, and the fragility of the overlap extensions; CGP gets many local instances, per-context choice, and freedom from orphans and diamonds, and pays with the wiring — there is no search, so the selection must be written down. - -Neither is better in the abstract, and the honest positioning names where each wins. When a program genuinely wants one canonical instance per type program-wide — one `Ord`, one `Show`, one serialization, so that a `Set` cannot be corrupted and generic code needs no wiring — coherent type classes are the right tool, and simulating that with CGP's per-context wiring would be over-engineering that reintroduces by hand the uniqueness the compiler would give for free. When a program needs several interchangeable instances, per-deployment or per-context choice, instances for types and traits it does not own, or must escape the diamond and orphan problems, CGP's explicit selection is the better tool, and the coherence it discards was the very thing in the way. CGP's explicitness is, for its audience, a feature rather than a cost, in the spirit of the "explicit dictionaries" critics of coherence advocate: the choice is a greppable line in a wiring table, not the outcome of a resolution search that overlap or incoherence could quietly derail. - -## Presenting CGP to someone who knows this - -A reader who knows type classes holds essentially all of CGP's conceptual furniture, and the way in is to state the correspondence and then the one change. A **component is a class**, a **provider is an instance** — but a first-class, named one — **wiring is instance resolution made explicit**, an **impl-side dependency is a class constraint**, the **context is the dictionary** that carries them, and a component's **associated type is a class's associated type**. The one change is coherence: CGP removes the one-instance-per-type rule and, with it, automatic resolution, and puts explicit per-context selection in their place. Framed this way CGP is not a new paradigm to this reader but their own type-class system with coherence swapped for modular, per-context choice — the fully-modular end of the [modular type classes](#modular-type-classes-classes-as-signatures-instances-as-structures) spectrum. - -The expectations to correct are the two coherence buys. First, *automatic resolution*: this reader expects the compiler to find the instance by type, and CGP does not — the provider is named in a table. Present that as the deliberate consequence of the capability they will find most striking, which is that CGP hosts the overlapping and orphan instances their language forbids or makes fragile. Lead with the pains coherence causes them: the newtype wrapper to get a second `Monoid`, the orphan-rule module contortions, the `OVERLAPPING` pragmas that can still go incoherent, the `INCOHERENT` footgun, and — for the Agda or Lean reader — the diamond problem and the definitional-equality drudgery of keeping overlapping instances aligned. Each of these disappears when instances are distinct marker types selected per context, and the pitch that lands is *overlapping and incoherent instances, made safe*: the freedom those extensions reach for, with the indeterminism designed out because the choice is explicit and local rather than searched and global. - -Second, *global uniqueness*: this reader may expect that once a type has an instance, it is the instance everywhere, so a `Set` is safe. CGP deliberately does not promise this program-wide — it promises it *per context*. Say so plainly, because a reader who assumes global uniqueness will look for a guarantee CGP scopes rather than globalizes; then frame the scoping as the point, since it is what lets two contexts serialize the same type two ways without conflict. For the reader who has read *The trouble with typeclasses* or fought a coherence bug, the framing is that CGP is the explicit-dictionary design they wished for, with the ergonomics of a wiring table and the compile-time verification of [`check_components!`](../reference/macros/check_components.md) standing in for the resolution they gave up. - -## Sources - -The account of the related work draws on the primary literature on type classes and their coherence, the official documentation of GHC, Agda, and Lean, and cited community writing for sentiment; the CGP snippets are drawn from the knowledge base's [bypassing coherence](../concepts/coherence.md), [consumer and provider traits](../concepts/consumer-and-provider-traits.md), and [modular serialization](../examples/modular-serialization.md) material and verified against current macro behavior. - -- [Wadler & Blott, *How to make ad-hoc polymorphism less ad hoc* (POPL 1989)](https://dl.acm.org/doi/10.1145/75277.75283) ([PDF](http://users.csc.calpoly.edu/~akeen/courses/csc530/references/wadler.pdf)) — the origin of type classes and the dictionary-passing translation that compiles a class into a record of methods passed as a hidden argument. -- [GHC User's Guide — Instance declarations and resolution](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/instances.html) — the one-instance rule, the orphan-instance rule, the `OVERLAPPING`/`OVERLAPPABLE`/`OVERLAPS`/`INCOHERENT` pragmas, and GHC's own warnings that overlap can give rise to incoherence and that incoherent instances are indeterministic. -- [Yang, *Type classes: confluence, coherence and global uniqueness*](https://blog.ezyang.com/2014/07/type-classes-confluence-coherence-global-uniqueness/) — the decomposition of coherence into confluence, coherence, and global uniqueness, the `Set`/`Ord` safety argument, and the newtype workaround and its breakdown. -- [Rust RFC 2451 — re-rebalancing coherence](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) and [*Type class* (Wikipedia)](https://en.wikipedia.org/wiki/Type_class) — Rust traits as type classes with an *enforced* orphan rule, versus Haskell's discouraged-by-convention orphans. -- [Devriese & Piessens, *On the Bright Side of Type Classes: Instance Arguments in Agda* (ICFP 2011)](https://dl.acm.org/doi/10.1145/2034574.2034796) — Agda's instance arguments as call-site-scoped, type-directed resolution over dependently-typed records, without a coherence guarantee. -- [Selsam, Ullrich & de Moura, *Tabled Typeclass Resolution* (Lean)](https://arxiv.org/pdf/2001.04301) and [Baanen, *Use and abuse of instance parameters in the Lean mathematical library*](https://arxiv.org/pdf/2202.01629) — Lean's priority-based backtracking resolution, the diamond problem, and Mathlib's discipline of keeping overlapping instances definitionally equal. -- [Dreyer, Harper & Chakravarty, *Modular Type Classes* (POPL 2007)](https://people.mpi-sws.org/~dreyer/papers/mtc/main-long.pdf) — classes as signatures and instances as structures and functors, scoped canonicity for implicit resolution, and the canonicity-versus-modularity tension that places CGP at the fully-modular extreme. -- [Chiusano, *The trouble with typeclasses*](https://pchiusano.github.io/2018-02-13/typeclasses.html), [Racordon, *On the State of Coherence in the Land of Type Classes*](https://arxiv.org/pdf/2502.20546), and [Queensland FP Lab, *Multi-Parameter Type Classes and their Orphan Rules*](https://qfpl.io/posts/orphans-and-fundeps/) — community sentiment on coherence, the case for explicit dictionaries, and the orphan-instance pain. diff --git a/docs/skills/README.md b/docs/skills/README.md deleted file mode 100644 index 2b5767e0..00000000 --- a/docs/skills/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# CGP Agent Skills - -This directory holds the [agent skills](https://agentskills.io/) built from the CGP knowledge base — self-contained guides an LLM coding agent loads to read, write, debug, and explain Context-Generic Programming code. Where the [reference/](../reference/README.md), [concepts/](../concepts/README.md), and [examples/](../examples/README.md) directories are the exhaustive, version-controlled record, a skill is the distilled working subset of that record: enough for an agent to become proficient without loading everything, organized for progressive disclosure so the agent reads only the parts a task needs. - -## How a skill differs from the rest of the knowledge base - -A skill is a *teaching artifact optimized for an agent's context window*, whereas a reference document is an *exhaustive per-construct record* and a concept document is a *cross-cutting overview*. The skill draws on all three but reproduces none of them wholesale — it carries the mental model, the common constructs, and worked examples in enough depth to act, and points to the online knowledge base for the corner cases it deliberately omits. The relationship is one-directional: the knowledge base is the source of truth, and the skill is a synthesis kept in sync with it. - -A skill is also deployed differently. It is copied out of this repository and run on its own, so it cannot rely on any file outside its own directory — every cross-link is either to a sibling sub-skill (a plain relative filename) or to the online knowledge base on GitHub. The authoring and synchronization rules that govern this, including how to keep a skill current when a construct changes, live in [../AGENTS.md](../AGENTS.md) under "The skills directory." - -## The catalog - -- [`cgp`](cgp/SKILL.md) — the foundational skill for working with CGP in Rust. Its `SKILL.md` establishes the paradigm (the consumer/provider trait split, wiring, impl-side dependencies) and routes to a set of topic sub-skills under [cgp/references/](cgp/references/) covering components, wiring, checking, functions and getters, abstract types, higher-order providers, error handling, handlers, extensible data, namespaces, the type-level primitives, and the modularity hierarchy. It is the skill `/cgp` resolves to. diff --git a/docs/skills/cgp/SKILL.md b/docs/skills/cgp/SKILL.md deleted file mode 100644 index d0f9841e..00000000 --- a/docs/skills/cgp/SKILL.md +++ /dev/null @@ -1,1051 +0,0 @@ ---- -name: cgp -description: >- - Read, write, debug, and explain Context-Generic Programming (CGP) code in Rust. - Use this skill whenever you encounter or are asked to work with CGP — any code that - uses `cgp::prelude::*`, the `#[cgp_component]`, `#[cgp_impl]`, `#[cgp_provider]`, - `#[cgp_fn]`, `#[cgp_type]`, `#[cgp_getter]`, `#[cgp_auto_getter]`, `#[cgp_computer]`, - `#[cgp_producer]`, or `cgp_namespace!` macros, the `delegate_components!`, - `check_components!`, or `delegate_and_check_components!` macros, the `Symbol!`, - `Product!`, `Sum!`, or `Path!` type-level macros, the `HasField`/`HasFields` traits or - their derives, providers such as `UseContext`/`UseDelegate`/`UseField`/`UseType`, the - handler family (`Computer`/`Producer`/`Handler`), or terms like consumer trait, - provider trait, provider, wiring, impl-side dependency, or context-generic. Trigger it - even when the user does not say "CGP" by name but is clearly working with these - constructs, when a Rust trait error mentions `IsProviderFor`/`DelegateComponent`, or - when someone wants modular, dependency-injected, multiple-implementation Rust traits. ---- - -# Context-Generic Programming (CGP) in Rust - -CGP is a modular programming paradigm for Rust that works around the language's coherence -restrictions, letting you write many overlapping or "orphan" trait implementations and then -choose which one applies by **wiring** them onto a concrete context type. This file is a -self-contained primer: read it top to bottom and you hold the whole mental model — every core term, -the shape of every construct, and enough of each expansion to read, write, and debug the majority of -CGP code. It is longer than a typical skill on purpose, because CGP's surface is broad and an agent -who stops here should still be competent, not lost. - -**This primer is the map, not the territory — load the sub-skill before you act on any construct it -covers.** The primer gives you the shape of each construct; the `references/` sub-skills give you the -exact grammar, the full expansion, the corner cases, and the worked examples that keep you from -writing subtly wrong code. The gap between the two is where mistakes live: the primer tells you -`#[cgp_impl]` writes a provider, but [components](references/components.md) tells you what `self` -rewrites to, [macro-grammar](references/macro-grammar.md) tells you which attribute forms parse, and -only reading them stops you from emitting an impl that fails to compile in a way the error message -will not explain. So before you write, modify, review, or debug code using a construct — even one -that looks simple — **open the sub-skill that owns it.** Each entry in [the sub-skill -index](#sub-skills-load-the-one-that-owns-your-task) below says exactly what it adds beyond this -primer, so you can tell what you would be guessing at without it. Treat "I already read the primer" -as insufficient: the primer is calibrated to make you *know what you do not know*, and the sub-skill -is where that gap is closed. - -CGP is implemented almost entirely as procedural macros. Every macro desugars to ordinary Rust -traits and impls, so the reliable way to understand any construct is to know the code it expands -to. This primer shows those expansions where they matter and keeps them out of the way where they -do not. - -## Tooling: use cargo-cgp for readable errors and expansions - -**Before debugging any CGP compile error, use `cargo-cgp` if it is available.** It has two commands -you will want: `check`, which reshapes the errors, and -[`expand`](#reading-what-a-macro-generated-cargo-cgp-expand), which shows the code the macros generated. `cargo-cgp` is CGP's -first-class toolchain — a cargo subcommand that stands in for `cargo check` and rewrites CGP's -compiler errors into a compact, root-cause-first form, tagging each rewritten message with a -`[CGP-Exxx]` code. A raw CGP error is often a wall of generated types with the real cause buried or -hidden entirely; `cargo-cgp` names the cause and renders the dependency chain that leads to it. -Recommend it to the user for building and checking CGP code, and prefer it over plain `cargo check` -whenever a wiring failure needs diagnosing. - -**Check whether it is installed** with `cargo cgp --version` (or just run `cargo cgp check` in a CGP -project). If the subcommand is missing, it is not installed. - -**If it is not installed, recommend installing it** — tell the user it makes CGP errors far more -readable — and **install it on their behalf only with their approval**, since it provisions a nightly -toolchain and builds a compiler-linked driver (heavy). - -**Prefer the cargo path**, which is the default for almost every machine: `cargo install cargo-cgp` -then `cargo cgp setup` (the first installs the small front-end on whatever toolchain is present; -`setup` provisions the pinned nightly and builds the matching driver in lockstep). It needs rustup. - -**Reach for Nix only if the host actually has it** — a `nix` command on `PATH` or a `flake.nix` in the -project — or if the user explicitly asks for it; otherwise do not bring Nix up at all. When Nix *is* -present but cargo-cgp is not installed, the lightest move is to **not install** and instead run the -tool through the flake from the project directory: -`nix run github:contextgeneric/cargo-cgp/v0.1.0-alpha -- check` (arguments after `--` go to -`cargo check`). To install into a Nix profile instead, use -`nix profile install github:contextgeneric/cargo-cgp/v0.1.0-alpha`. - -cargo-cgp requires a specific pinned Rust nightly, installed by `cargo cgp setup` (or built by the -Nix flake), and forces it only for its own check, so the user's project keeps its own toolchain -untouched. Full instructions: -. - -**Using it:** run it wherever you would run `cargo check` (arguments after `check` are forwarded): -`cargo cgp check`, `cargo cgp check --workspace`. It keeps rustc's own error code and adds a -`[CGP-Exxx]` tag, leading with the root cause over a `cargo tree`-style dependency chain; the codes -are catalogued at . - -### Reading what a macro generated: `cargo cgp expand` - -**When a CGP error stops making sense, read the code the macros produced rather than reasoning about -what you think they produce.** A wiring failure is always "the emitted impls do not resolve," and the -impls are generated, so the fastest way past a confusing diagnostic is often to look at them. -`cargo cgp expand` prints the crate after macro expansion, with CGP's type-level constructs -**resugared** — a field tag reads `Symbol!("width")`, not the raw `Symbol<5, Chars<'w', …>>` -spine the compiler prints; a pipeline reads `Product![StepOne, StepTwo]`; a namespace key reads -`Path!(@app.GreeterComponent)` — so the generated code is legible in the same vocabulary as the -source. - -Reach for it in four situations, each one where the answer is in the generated code rather than in the -message: - -- **A diagnostic names a type you did not write** (`IsProviderFor<…>`, a `PathCons<…>` key, a - `__Context__` parameter) and you need to see the impl it came from. -- **A wiring table does not resolve** and you want the real `DelegateComponent` keys and `Delegate` - values it produced, rather than inferring them from the table's syntax. -- **You are unsure what a construct emits** — the exact `where` clauses of a provider impl, whether a - getter's blanket impl requires the field you think it does. Reading the expansion beats guessing, and - beats trusting a remembered expansion. -- **Two forms differ and only one compiles.** Expand both and diff; the delta is the bug. - -Run it on one target, narrowed to what you care about: - -```sh -cargo cgp expand --lib # the whole library target -cargo cgp expand --lib --item contexts::MockApp # one module, type, or trait -``` - -**Target selection is required when a package has several targets** — pass `--lib` or `--bin NAME`, or -cargo declines with "extra arguments to `rustc` can only be passed to one target". Other arguments -(`-p`, `--features`) forward to `cargo rustc`, and the expansion goes to stdout, so redirect it to a -file when it is long and read that instead of flooding your context. - -**`--item ` is what makes the output manageable**, and which of three rules applies depends on -what the path names. The path is `::`-separated, may carry a leading `crate::`, and names something -inside the crate being expanded: - -- a **module** → its contents (`--item contexts`); -- a **type** → its declaration and every impl written *for* it. For a context that is its struct, the - `HasField`/`HasFieldMut` impls the derive generated, and its `DelegateComponent` wiring entries with - the real key and provider types (`--item Rectangle`); -- a **trait** → its definition and every impl *of* it, which is usually the rule you want. Naming a - component's *provider* trait (`--item AreaCalculator`) gives the provider trait, the delegation - blanket impl, the `UseContext` and `RedirectLookup` impls, and each wired provider's impl; naming the - *consumer* trait (`--item CanCalculateArea`) gives just the consumer trait and its routing blanket. - -Two limits matter when you read the output. **`expand` is not a check**: it stops once the macros are -expanded, so it reports nothing about wiring — use `cargo cgp check` for that, and expect `expand` to -succeed even on a crate that does not type-check (which is exactly what makes it useful mid-debugging). -And the output is for reading, not compiling: the `cgp::macro_prelude::` qualifier is stripped, and an -`open` statement's per-key entry keeps its raw `PathCons<…>` key. - -One availability note: `expand` is newer than cargo-cgp v0.1.0-alpha, so a crates.io install does not -carry it yet. Until the next release it comes from the Nix flake without a tag -(`nix run github:contextgeneric/cargo-cgp -- expand --lib`) or from a source checkout; see -. - -**Scope:** cargo-cgp is optional and adds only `check` and `expand` — there is no -`cargo cgp build`/`run`/`test`, so build, run, and test the project with plain cargo. CGP itself -compiles on any **stable Rust ≥ 1.89**, so plain `cargo check` works on a CGP project too; reach for -`cargo cgp check` specifically when you hit or expect a wiring error and want it made readable. - -`cargo-cgp` can also be Rust Analyzer's on-save check backend, via -`rust-analyzer.check.overrideCommand` set to -`["cargo","cgp","check","--workspace","--all-targets","--message-format=json"]`. But **never modify -the user's Rust Analyzer settings implicitly** — mention this option and let them opt in, and only -edit their editor configuration when they explicitly ask you to. - -**When cargo-cgp is not available, or leaves an error largely unrewritten**, fall back to reading the -raw compiler output by hand — and only then load the [error-extraction sub-skill](references/error-extraction.md), -the technique for reducing a raw CGP cascade to its root cause. When cargo-cgp *has* reshaped the -error, its `[CGP-Exxx]` headline and root-cause tree are already the compact summary that sub-skill -would produce, so you do not need it. - -### Versions and keeping this skill current - -This skill is written for **CGP v0.8.0** and **cargo-cgp v0.1.0-alpha**. Check both on the host and -act on a mismatch: - -- Read the user's `cgp` version (its `Cargo.toml`/`Cargo.lock` entry) and run `cargo cgp --version` - for the tool. -- **If either is older than recorded here**, recommend updating it — `cargo update -p cgp` for the - library, `cargo cgp update` (or a Nix flake refresh) for the tool — so its behavior matches this - skill. -- **If the host's `cgp` is *newer* than v0.8.0**, this skill is behind the library: tell the user the - **skill should be upgraded** to match their `cgp`, rather than changing their code to fit an older - skill. Treat the newer `cgp` as authoritative and flag the gap instead of guessing. - -## The problem CGP solves - -Rust's coherence rules permit at most one implementation of a trait for a given type, and forbid -implementing a foreign trait for a foreign type (the orphan rule). This makes it hard to offer -several interchangeable implementations of one interface, or to let a downstream crate choose an -implementation for a type it does not own. CGP sidesteps both limits with a two-trait split: an -implementation is written against a **provider trait** whose `Self` is a dummy marker type the -crate owns, so coherence never blocks it, and a concrete **context** type later picks which -provider it uses through a small type-level table. The choice is local to the context, so two -different contexts can wire the same interface to different implementations. - -## Blanket traits and impl-side dependencies - -CGP grows out of blanket trait implementations (extension traits), and the single most important -idea to internalize is the **impl-side dependency**: a blanket impl can require constraints in its -`where` clause that are *not* part of the trait interface. Those hidden requirements are the -paradigm's form of dependency injection. For example: - -```rust -pub trait CanGreet { - fn greet(&self); -} - -pub trait HasName { - fn name(&self) -> &str; -} - -impl CanGreet for Context -where - Context: HasName, -{ - fn greet(&self) { - println!("Hello, {}!", self.name()); - } -} -``` - -`CanGreet` hides its `HasName` dependency inside the blanket impl, so a caller that is itself -generic does not have to forward `HasName` the way it would with a free generic function. In CGP -you typically start by writing generic logic as a blanket trait like this, and promote it to a -full CGP component later only when you need more than one implementation. Blanket traits are not -themselves CGP components, but the technique recurs throughout CGP. - -## The core vocabulary - -CGP code is readable once you hold five terms in mind. A **consumer trait** is the ordinary, -`self`-style trait you call (`CanGreet`, `CanCalculateArea`); it reads as a verb (`CanDoX`). A -**provider trait**, generated by `#[cgp_component]`, is the same interface with `Self` moved to an -explicit `Context` generic parameter (`Greeter`, `AreaCalculator`); it reads as -a noun (`SomethingDoer`, or `…Provider` when no noun fits). A **provider** is a zero-sized marker -struct (e.g. `GreetHello`, `RectangleArea`) that implements a provider trait — it carries no -runtime value and exists only as a name at the type level. **Wiring** is the act of telling a -context which provider implements each component, recorded in a type-level table. An -**impl-side dependency** is a `where`-clause constraint a provider needs but the consumer trait -does not expose. A **component** is the whole bundle a macro generates: consumer trait, provider -trait, and a `…Component` marker type that keys the wiring table. - -The duality is the crux: a consumer trait is what you *use*, a provider trait is what you -*implement*, and the macro-generated blanket impls connect them so that wiring a context to a -provider makes the context implement the consumer trait. When you see a provider trait method take -`context: &Context` where the consumer took `&self`, that is the same method — `self` became -`context`. - -A persistent source of confusion to avoid: inside a provider, `self`/`Self` (in `#[cgp_impl]`) or -the `context`/`Context` parameter (in the raw provider-trait form) always refer to the **context**, -never to the provider struct. The provider struct is a pure type-level name with no fields and no -runtime value — you cannot store state in it, and any attempt to read a "field" of a provider at -runtime is a mistake. - -## Reading CGP code on sight - -To follow most CGP code you only need to recognize a handful of shapes: - -- `#[cgp_component(Greeter)] trait CanGreet { fn greet(&self); }` defines a component. `CanGreet` - is the consumer trait you call; `Greeter` is the provider trait implementations target; - `GreeterComponent` is the wiring key. -- `#[cgp_impl(new GreetHello)] impl Greeter where Self: HasName { fn greet(&self) { … } }` writes a - provider named `GreetHello` for the `Greeter` component. Inside, `self`/`Self` mean the - *context*, and the `where` clause lists impl-side dependencies. -- `#[cgp_fn] fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { … }` - defines a single-implementation capability as a blanket-impl trait, pulling `width`/`height` from - the context's fields automatically. -- `delegate_components! { Person { GreeterComponent: GreetHello } }` wires the `Person` context: it - says "for the `Greeter` component, `Person` uses the `GreetHello` provider." After this, `Person` - implements `CanGreet`. -- `check_components! { Person { GreeterComponent } }` is a compile-time assertion that the wiring is - complete and all transitive dependencies are satisfied. - -A consumer trait can also be implemented directly on a context like any normal Rust trait -(`impl CanGreet for Person { … }`) — CGP traits are a superset of vanilla traits, and the macros -only save boilerplate. - -## Which construct to use: prefer this, not that - -When two constructs can express the same thing, CGP has a preferred one, and choosing wrong produces -code that compiles but reads as dated or misuses an advanced tool. This table is the quick answer, so -you pick the right pattern even without reading further. Each preference is a default with narrow -exceptions, spelled out under [Writing providers](#writing-providers) below and, with full before/after -examples, in the [modern-idioms](references/modern-idioms.md) sub-skill; the "avoid" column is not -wrong, it is what you *read* in generated code and legacy wiring, not what you *write* anew. - -| To… | Prefer | Not (legacy / advanced / read-only) | -|---|---|---| -| write a provider | `#[cgp_impl]`, header `impl Trait` (omit `for Context`) | raw `#[cgp_provider]` / `#[cgp_new_provider]` | -| read a field from your own context | an `#[implicit]` argument | `#[cgp_auto_getter]` / any getter trait declared just to read it | -| declare a getter (field on *another* type, or a named shared capability) | `#[cgp_auto_getter]`, used sparingly | `#[cgp_getter]` (only for per-context field choice) | -| require a capability | `#[uses(Trait)]` | `where Self: Trait` | -| require an inner provider | `#[use_provider(P: Trait)]` | `where P: Trait` | -| name an abstract type (e.g. `Error`) | `#[use_type(Trait.Type)]` + the bare alias | `: Trait` supertrait + `Self::Type` | -| pass several args to `#[uses]` / `#[use_type]` / `#[use_provider]` | one attribute, comma-separated | repeating the same attribute | -| add a capability supertrait | `#[extend(Trait)]` | native `pub trait …: Supertrait` | -| dispatch a component per type | the `open` statement (or a namespace) | `#[derive_delegate]` + `UseDelegate` tables | -| verify a context is fully wired | separate `check_components!` (or `delegate_and_check_components!` for a basic starter context) | leaving a context's wiring unchecked | -| build a field/list/string/path type | `Symbol!` / `Product!` / `Sum!` / `Path!` sugar | hand-written `Cons`/`Nil`/`Chars`/`Either`/`PathCons` | - -Two names are gone entirely, not merely dated: never write `#[cgp_context]` (removed — assemble a -context with `delegate_components!` and the derives instead) or `ProvideType` (renamed to -`TypeProvider`). One caveat carries across the whole table: a construct's *own* local associated type -stays qualified as `Self::Output` — only an *imported* abstract type is written bare via `#[use_type]`. - -## The prelude and version - -Almost everything CGP exports comes through one import, which belongs at the top of every module -that uses CGP: - -```rust -use cgp::prelude::*; -``` - -This skill describes CGP **v0.8.0** (and cargo-cgp **v0.1.0-alpha** — see -[Tooling](#tooling-use-cargo-cgp-for-readable-errors) for checking both versions on the host and -reconciling a mismatch). A few names are intentionally *not* in the prelude and must be -imported from their module — most notably the error-handling wiring keys and backends (see Error -handling below). Inside documentation code blocks you may omit the prelude import for brevity. - ---- - -# Components: the heart of CGP - -A component is what `#[cgp_component]` builds from one trait so that *using* a capability and -*implementing* it become separate, swappable things. Applying it to a consumer trait: - -```rust -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} -``` - -generates five items, of which you write or call only the first. The **consumer trait** `CanGreet` -is emitted unchanged — callers write `person.greet()`. The **provider trait** is the same interface -with `Self` moved to a leading `Context` parameter and `self` rewritten to `context`: - -```rust -pub trait Greeter: IsProviderFor { - fn greet(context: &Context); -} -``` - -The **component marker** `pub struct GreeterComponent;` is the zero-sized key into the wiring table. -Two **blanket impls** connect the sides: one makes any context that implements the provider trait -*for itself* automatically implement the consumer trait; the other lets a context that delegates -this component (via `DelegateComponent`) inherit the provider trait from whatever provider it -delegates to. You never write these blanket impls; they are the routing machinery, and it is enough -to think of wiring as a table lookup. Crucially, that lookup is resolved **entirely at compile -time**: the table is a set of trait impls, so the compiler picks the provider during type -resolution and monomorphizes the call to a direct, statically-dispatched one. CGP wiring is -therefore zero-cost — there is no runtime table, no dynamic dispatch, and no `vtable` in the -generated code, even though "table lookup" is a useful mental model. In real generated code the -context parameter is named `__Context__` and the provider parameter `__Provider__` (reserved -identifiers chosen so they never clash with your types); the names `Context`/`Provider` here are -for readability. - -The attribute's argument sets those three generated names. The bare `#[cgp_component(Greeter)]` form -names only the provider trait; the component marker defaults to that name plus `Component` -(`GreeterComponent`) and the context to `__Context__`. A key/value form with brace delimiters -overrides any of the three — `#[cgp_component { name: GreeterComponent, provider: Greeter, context: Context }]` -— where only `provider` is required. One limitation to know: a **const generic parameter** on the -trait is rejected, because a component's extra parameters are recorded as a tuple of *types* in -`IsProviderFor` and a const value has nowhere to live there (an associated `const` *item* on the -trait is fine — it is supplied by a const-generic provider struct as usual). See -[macro-grammar](references/macro-grammar.md) for the full argument grammar and -[components](references/components.md) for the complete expansion. - -## `IsProviderFor` and error messages - -`IsProviderFor` is an empty marker trait that rides as a supertrait on -every provider trait. Its only purpose is good error messages: a provider lists its dependencies in -a `where` clause, and the macros implement `IsProviderFor` for the provider under the *same* bounds, -so when a dependency is unmet the compiler can name the missing bound instead of vaguely saying "the -trait is not implemented." When you see an error that some provider does not implement -`IsProviderFor<…>`, read it as "the provider trait is not implemented, because the named dependency -is missing." You never write `IsProviderFor` yourself — the provider macros generate it. - -## Writing providers - -A provider can be written at three levels of sugar over the same machinery. **`#[cgp_impl]` is the -form to prefer**, because it lets you write the provider in consumer-style syntax — keeping `self`, -`Self`, and the consumer method signatures — and the macro rewrites it into the provider-trait -shape: - -```rust -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasName, -{ - fn greet(&self) { - println!("Hello, {}!", self.name()); - } -} -``` - -The provider name goes in the attribute argument; a leading `new` keyword also declares the -`struct GreetHello;`. **Prefer the unqualified `impl Greeter` form and let the macro insert the -context parameter** — omitting `for Context` is what makes a provider read like an ordinary trait -impl. Write the explicit `impl Greeter for Context` only when you must bound or name the -context readably (for example a lifetime or HRTB the sugar cannot express); it must then be declared -in the impl generics. Remember that `self`/`Self` here mean the context. `#[cgp_impl]` desugars to: - -```rust -#[cgp_new_provider] -impl Greeter for GreetHello -where - Context: HasName, -{ - fn greet(context: &Context) { - println!("Hello, {}!", context.name()); - } -} -``` - -The lower forms are what you mostly *read* rather than write. `#[cgp_provider]` is applied to a -provider-trait impl written directly on an existing provider struct; it passes the impl through and -auto-generates the matching `IsProviderFor` impl from the same `where` clause. -`#[cgp_new_provider]` is the same but also declares the provider struct (a generic provider gets a -`PhantomData` field over its parameters, e.g. `pub struct Multiply(PhantomData);`). -The attribute argument can override the component name, which otherwise defaults to the provider -trait's name plus `Component`. One special `#[cgp_impl]` form, `#[cgp_impl(Self)]`, bypasses the -provider rewrite entirely and emits the block as a *direct* consumer-trait impl on the concrete -context (the `for Context` clause is then required) — useful when you want to implement a consumer -trait by hand while still applying companion attributes such as `#[use_provider]`. - -**Strongly prefer the modern, vanilla-looking idioms when you write CGP, and reach for the explicit -forms only when a construct genuinely cannot express the case.** Each idiom below trades a piece of -visible machinery for syntax that reads like ordinary Rust: - -- **Write providers with `#[cgp_impl]`** (not `#[cgp_provider]`/`#[cgp_new_provider]`), omitting - `for Context` so the header reads `impl Greeter`. -- **Declare dependencies with attributes, not hand-written bounds:** capability dependencies with - [`#[uses(...)]`](references/functions-and-getters.md), inner-provider dependencies with - [`#[use_provider(...)]`](references/higher-order-providers.md), instead of raw `Self:` / - `Provider: …` `where` clauses. When one of these attributes — or - [`#[use_type]`](references/abstract-types.md) — carries several arguments, put them all in one - attribute separated by commas (`#[uses(A, B)]`, `#[use_type(T.X, U.Y)]`) rather than stacking the - same attribute repeatedly; one attribute reads as a single dependency list. -- **Read context fields with [`#[implicit]`](references/functions-and-getters.md) arguments** rather - than a getter trait — this is the default for *any* field a provider reads from its own context, - including one several providers each read. An implicit argument reads only from `self` and takes a - plain `&T` by reference without cloning. Use `#[cgp_auto_getter]` sparingly, only where an implicit - argument cannot reach: a getter for a field on *another* type required as a `where` bound on it - (`Request: HasBasicAuthHeader`), an accessor other code depends on as a named capability, or a - getter carrying an associated type inferred from the field. Reserve `#[cgp_getter]` for the advanced - case of choosing the source field per context. -- **Add non-type capability supertraits with [`#[extend(...)]`](references/functions-and-getters.md)** - rather than native `: Supertrait` syntax, which reads as OOP-style inheritance rather than a - capability import. -- **Import abstract types with [`#[use_type]`](references/abstract-types.md)**, writing the bare - alias (`Scalar`, `Error`) instead of a hand-written `: HasScalarType` supertrait and a qualified - `Self::Scalar` at every use. This holds even in `#[cgp_component]` definitions: prefer - `#[use_type(HasErrorType.Error)]` over `: HasErrorType` + `Self::Error`. When a provider *pins* an - abstract type to a concrete one — a `where Self: HasErrorType` clause — express - that with the equality form `#[use_type(HasErrorType.{Error = AppError})]`, which emits the same - `Self: HasErrorType` bound; the right-hand side may even name another imported - alias (`#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` - unifies two abstract types). The equality form is a `#[cgp_impl]`/`#[cgp_fn]` tool — it is rejected - on `#[cgp_component]`. -- **Dispatch a generic-parameter component with the `open` statement or a namespace**, skipping - `#[derive_delegate]`/`UseDelegate` when defining a new component. - -The explicit forms remain correct and are what you *read* in generated code and desugaring; the -exceptions that still need them are narrow — an associated-type-equality bound on a **non-abstract-type** -trait (`Iterator`, `From`), which `#[use_type]` cannot spell and which reads more clearly -as an explicit `where` clause than crammed into an import-shaped `#[uses]` (which now accepts it), a -lifetime or HRTB that forces a named context, or a **local** associated type such as `Self::Output`, -which stays qualified because it is the trait's own type, not an imported abstract one. Do *not* leave an -equality bound on an **abstract-type** trait (`Self: HasErrorType`) as a hand-written -`where` clause — that is exactly what the `#[use_type]` equality form `#[use_type(HasErrorType.{Error = AppError})]` -replaces; only equality on a trait you would never `#[use_type]` from stays an explicit `where`. For the full legacy-to-modern before/after mapping of each idiom — the -reference to load whenever you read or modernize existing CGP — see -[modern-idioms](references/modern-idioms.md). - -The provider's `where` clause is where **impl-side dependencies** live: `GreetHello` requires -`Self: HasName`, but `CanGreet` exposes no such bound, so a caller bounding on `CanGreet` never sees -`HasName`. The wiring satisfies each dependency by resolving it through the same context. - -A consumer trait is still an ordinary trait: when you don't need multiple implementations, write -`impl CanGreet for Person { … }` directly and skip the provider machinery entirely. - ---- - -# Wiring: connecting a context to providers - -Wiring records, on a context type, which provider supplies each component. The underlying mechanism -is the `DelegateComponent` trait — a type-level table whose key is the `…Component` marker and whose -`Delegate` associated type is the chosen provider — but you almost always write it through -`delegate_components!`: - -```rust -#[derive(HasField)] -pub struct Person { - pub name: String, -} - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} -``` - -After this, `Person` implements `CanGreet` and `person.greet()` resolves through the table to -`GreetHello`. Swapping the table entry is the only change needed to swap behavior. - -The macro has a few shorthands. An **array key** maps several components to one provider: -`[FooComponent, BarComponent]: FooBarProvider`. A leading **`new`** keyword -(`delegate_components! { new MyComponents { … } }`) also defines `struct MyComponents;`, which is how -you build an **aggregate provider** — a zero-sized provider that holds a table dispatching each -component to a sub-provider, so other contexts can delegate a whole group of components to it as one -reusable unit. A leading **generic list** (`delegate_components! { MyContext { … } }`) wires a -whole family of contexts at once. - -The target of `delegate_components!` is therefore not always a context: it is either a concrete -context (as `Person` is above) or an aggregate provider (as `MyComponents` is). This distinction -governs checking — an aggregate provider is dispatched *to* by contexts and is never its own context, -so it must be wired with plain `delegate_components!` and never `delegate_and_check_components!`; the -next section explains why. - -To understand what wiring *does*, picture the explicit version: `delegate_components!` is equivalent -to implementing the consumer trait by hand and forwarding to the provider — -`impl CanGreet for Person { fn greet(&self) { >::greet(self) } }`. -The macro just generates that plumbing (plus the `IsProviderFor` propagation) for you. - -## `UseContext` - -`UseContext` is a provider that implements a provider trait by routing back through the context's -*own* consumer-trait impl — the dual of the consumer blanket impl. Wiring a component to -`UseContext` means "use whatever this context already does for this trait," which is mainly useful -as the default inner provider of a higher-order provider (below). Delegating a component directly to -`UseContext` when the context's only implementation of that component *is* that delegation creates a -circular dependency and fails to compile. - -## Dispatching a generic-parameter component per type with `open` - -When a component is generic over a type parameter, you often want a different provider per value of -that parameter. The modern, preferred way is the **`open` statement** inside `delegate_components!`. -Given a component `CanCalculateArea` (provider `AreaCalculator`), a context dispatches per -shape like this: - -```rust -delegate_components! { - MyApp { - open AreaCalculatorComponent; - - @AreaCalculatorComponent.Rectangle: RectangleArea, - @AreaCalculatorComponent.Circle: CircleArea, - } -} -``` - -The `open … ;` header opens one or more components for per-value wiring and **must lead** the -block (it comes before any plain `Component: Provider` mappings, or the macro fails to parse). The -braces are optional when opening a single component (`open AreaCalculatorComponent;`); use the -braced list `open { A, B };` to open several at once. Each -`@Component.Key: Provider` entry then assigns a provider for one value of the dispatch parameter; a -brace group on the final segment shares one provider across several values -(`@AreaCalculatorComponent.{u32, u64, bool}: SomeProvider`), and a key may carry generics -(`@SomeComponent.<'a, T> &'a T: SomeProvider`). `open` works through the `RedirectLookup` impl that -every `#[cgp_component]` already generates, so it needs no extra attribute on the component. It does -not combine with a joined namespace (`#[prefix(...)]`); that is the full namespace feature. - -**Legacy form (read but don't write):** older code dispatches the same way by wrapping a nested -table in the `UseDelegate` provider — -`AreaCalculatorComponent: UseDelegate` -— generated by a `#[derive_delegate(UseDelegate)]` attribute on the component. This still -works and is common in existing code, but it is slated for deprecation; prefer `open` for new code. -Note that some CGP-shipped components (the error and handler families) are still *defined* with -`#[derive_delegate]` in the library, so you will see `UseDelegate` tables wiring them. - ---- - -# Checking: verifying wiring at compile time - -CGP wiring is **lazy**: defining a `delegate_components!` entry does not itself check that the -provider's transitive dependencies are satisfied. A missing dependency therefore surfaces only when -the consumer trait is finally used, often as a confusing error. To catch it early and clearly, assert -the wiring with a check: - -```rust -check_components! { - Person { - GreeterComponent, - } -} -``` - -This generates a check trait whose supertrait is `CanUseComponent` for -`Person`; if `Person` cannot actually use the component, the compiler reports the missing dependency -at this site, walking through `IsProviderFor` so the real cause (e.g. a missing -`HasField`) is named rather than hidden. For a component with generic parameters, -list the parameter after the component (`GreeterComponent: Rectangle`), group multiple parameters as -a tuple (`(Rectangle, f64)`), and use array syntax to check several at once. - -`delegate_and_check_components!` fuses wiring and checking in one step, so every delegation is -verified the moment it is written: - -```rust -delegate_and_check_components! { - Person { - GreeterComponent: GreetHello, - } -} -``` - -Its check trait is named `__CanUse{Context}` (vs. `__Check{Context}` for `check_components!`), so -both macros can appear once per module without clashing; override with `#[check_trait(Name)]`. When -the delegated component has generic parameters, add `#[check_params(...)]` on the entry; skip a -single entry's check with `#[skip_check]`. - -**This fused macro is a convenience for basic wiring and for getting started — not the default for -advanced code.** It exists so a newcomer cannot forget to write a separate `check_components!` and -then hit confusing lazy-wiring errors, and it derives a check only for the plain `Component: Provider` -delegation form. It cannot easily derive checks for advanced mappings — generic-parameter dispatch -(the `open` statement and `@`-path keys), namespaces, or per-layer higher-order checks. **In larger, -more advanced codebases, keep `delegate_components!` and `check_components!` separate**, which gives -full control over what is checked: `#[check_providers(...)]` per provider layer, concrete parameters -for generic keys, and checks over opened or namespaced wiring. The one non-negotiable is that a -context's wiring *is* checked somehow; which macro you use to do it scales with the wiring's -complexity. - -One case makes `delegate_and_check_components!` not just unnecessary but wrong: an **aggregate -provider** (the `new MyComponents { … }` table above). That target is a provider other contexts -delegate to, not a context itself — it has no fields and never implements a provider trait with -itself in the context position — so the check's `CanUseComponent` assertion on it cannot hold and the -macro would report spurious failures. Wire an aggregate provider with plain `delegate_components!`; -it is verified indirectly when a real context that delegates to it is checked, or directly with a -`#[check_providers(...)]` block that asserts `IsProviderFor` on it. Finally, not every unsatisfied -bound is a CGP component — some are ordinary or blanket traits that `check_components!` cannot verify. - -For a nested [higher-order provider](references/higher-order-providers.md), checking the context -tells you a layer is broken but not which one. The `#[check_providers(...)]` attribute on a -`check_components!` table changes the assertion from `CanUseComponent` on the context to -`IsProviderFor` on each named provider, so a dependency missing only from the outer wrapper errors on -its line alone while one missing from the inner provider errors on both — pinpointing the layer. See -[checking](references/checking.md) for the debugging playbook. - ---- - -# Functions and getters: the ergonomic surface - -Most basic CGP reads and writes values from the context, and the constructs here make that look like -plain Rust. - -## `HasField` and `#[derive(HasField)]` - -`HasField` is tag-keyed field access. The `Tag` is a type-level name: `Symbol!("width")` for a -named field or `Index<0>` for a tuple field. `#[derive(HasField)]` generates one impl per field: - -```rust -#[derive(HasField)] -pub struct Rectangle { - pub width: f64, - pub height: f64, -} -// generates HasField and HasField -``` - -Field values are read with `self.get_field(PhantomData::)`; the `PhantomData` -carries the tag so type inference knows which field is meant. - -## `#[cgp_fn]` and `#[implicit]` arguments - -`#[cgp_fn]` turns one function into a single-implementation blanket-impl trait — the simplest entry -point to CGP. Arguments marked `#[implicit]` are removed from the signature and pulled from context -fields via `HasField`: - -```rust -#[cgp_fn] -fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} -``` - -This generates a `RectangleArea` trait (named from the function in PascalCase; override with -`#[cgp_fn(MyName)]`) with a blanket impl for any context that has `width: f64` and `height: f64` -fields. Implicit arguments get `.clone()` added automatically for owned values and `.as_str()` for -`&str`. Generic parameters on the function move to the trait and impl; the `where` clause becomes -impl-side dependencies on the impl only; generic *method* parameters are intentionally unsupported. -Prefer implicit arguments for basic code — they make CGP look like ordinary functions. - -## `#[uses]`, `#[extend]`, `#[extend_where]` - -`#[uses(TraitA, TraitB)]` (on `#[cgp_fn]` or `#[cgp_impl]`) imports `Self` trait bounds, read -like a `use` statement. The simple `Trait` form is idiomatic, but any `where`-clause bound is -accepted, including associated-type equality (`HasErrorType`) — prefer `#[use_type]`'s -equality form for abstract-type pins: - -```rust -#[cgp_fn] -#[uses(RectangleArea)] -fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { - self.rectangle_area() * scale_factor * scale_factor -} -``` - -`#[extend(Trait)]` adds *supertrait* bounds to the generated trait — the only way to add supertraits -in `#[cgp_fn]` (whose `where` clauses are impl-side dependencies), and the **preferred** way to add a -*non-type capability* supertrait on `#[cgp_component]` too: `#[extend(HasName)]` reads as importing a -capability, whereas the native `pub trait CanGreet: HasName` syntax reads as OOP-style inheritance -from a parent class, which a CGP supertrait is not. (When the supertrait is an abstract-type component -whose associated type the signatures name, prefer `#[use_type]` instead — it adds the supertrait *and* -rewrites the type, and is the recommended form for abstract-type components.) `#[extend_where(Bound)]` -adds `where` clauses to the generated trait definition (`#[cgp_fn]` only), and accepts arbitrary -predicates including associated-type equality, unlike `#[uses]`/`#[extend]`. A fourth `#[cgp_fn]`-only -attribute, `#[impl_generics(Param: Bound)]`, adds a bounded generic parameter to the generated *impl* -alone — not the trait — which is how a `#[cgp_fn]` body borrows a generic value it does not want to -expose as a trait parameter (e.g. `#[impl_generics(Name: Display)]` over an `#[implicit] name: &Name`). - -## Getters: `#[cgp_auto_getter]`, `#[cgp_getter]`, `UseField` - -An `#[implicit]` argument (above) is the default way to read a context field, so a getter trait is -used *sparingly* — only where an implicit argument cannot reach. Because an implicit argument reads -only from the provider's own `self` (and takes a plain `&T` by reference, no clone), it covers every -same-context read, even a field several providers each consume. A getter trait earns its keep in -three cases it cannot handle: a field that lives on a type *other* than the provider's context, where -the getter is required as a `where` bound on that type (`Request: HasBasicAuthHeader`, so there -is no `self` field to read); an accessor other code depends on as a *named* capability through -`#[uses(HasName)]` or a supertrait; and a getter carrying an *associated type inferred from the -field* so the type stays abstract for callers. - -`#[cgp_auto_getter]` generates a blanket getter impl over `HasField`, with the field name taken from -the method name, and is the getter form to prefer for those cases: - -```rust -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} -// blanket impl for any context with a `name` field; &str/&String shorthands handled -``` - -A single-getter trait may instead declare a local associated type used as the return type, inferred -from the field — `trait HasName { type Name; fn name(&self) -> &Self::Name; }`. - -`#[cgp_getter]` is an **advanced** tool, reserved for when a context needs full control over which -field a getter reads from — most getters should use `#[cgp_auto_getter]` or an implicit argument -instead. It is like `#[cgp_component]` but also provides a `UseField` blanket impl, so the -getter's source field can be chosen by *wiring* rather than fixed to the method name. The -`UseField` provider implements a getter by reading the field named `Tag`, which may differ from -the method name: - -```rust -delegate_components! { - Person { - NameGetterComponent: UseField, - } -} -// Person::name() now returns the `first_name` field -``` - -`UseFieldRef` is the `AsRef`/`AsMut`-based variant. Any getter can also be implemented by hand — the -macros only save boilerplate. - ---- - -# Abstract types - -CGP abstracts over types with associated types in components. `#[cgp_type]` is the dedicated macro -(use it instead of `#[cgp_component]` for an abstract-type trait): - -```rust -#[cgp_type] -pub trait HasNameType { - type Name; -} -``` - -This defaults the provider name to the type name plus `TypeProvider` (here `NameTypeProvider`, marker -`NameTypeProviderComponent`) and additionally generates a `UseType` blanket impl. A context fixes the -type by wiring the component to `UseType`: - -```rust -delegate_components! { - Person { - NameTypeProviderComponent: UseType, - } -} -// or directly: impl HasNameType for Person { type Name = String; } -``` - -The direct impl is just as valid and shows that abstract types are ordinary associated-type traits. - -The `#[use_type(HasScalarType.Scalar)]` attribute is the recommended way to *use* an abstract type -inside `#[cgp_fn]`/`#[cgp_impl]`/`#[cgp_component]`: it rewrites bare `Scalar` to the fully-qualified -`::Scalar` everywhere and adds the supertrait/where bound, removing `Self::` -boilerplate and ambiguity. CGP's built-in abstract-type component is `HasType` (provider -`TypeProvider`). - ---- - -# Higher-order providers - -A **higher-order provider** takes another provider as a generic parameter and constrains it with a -provider-trait bound, so its inner behavior is chosen by wiring rather than fixed: - -```rust -#[cgp_impl(new ScaledAreaCalculator)] -#[use_provider(InnerCalculator: AreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - let base_area = InnerCalculator::area(self); - base_area * scale_factor * scale_factor - } -} -``` - -`#[use_provider(InnerCalculator: AreaCalculator)]` completes the inner provider's bound by adding the -`Self` parameter for you (you write `: AreaCalculator`, it means `AreaCalculator`) and moves it -into the `where` clause — that is the *only* thing the attribute does. Inside the body you still call -the provider explicitly with the associated-function form `InnerCalculator::area(self)`; there is no -call-site rewriting. A context then chooses the inner provider when wiring, e.g. -`AreaCalculatorComponent: ScaledAreaCalculator`. - -A higher-order provider often defaults its inner parameter to `UseContext` -(`pub struct IterSumArea(PhantomData);`), so that when no inner provider is -named, the inner step falls back to the context's own wiring. Not every provider with a generic -parameter is higher-order: a provider like `GetName` that uses `Tag` only as a `HasField` key -(no provider-trait bound) is not. - -When a component itself is generic (`#[cgp_component(AreaCalculator)] trait CanCalculateArea`), -the provider trait appends the parameters after the context (`AreaCalculator`), -`IsProviderFor` groups them into its `Params` tuple, and lifetimes are lifted into the `Life<'a>` -type. Such a component is most useful for **cross-context dependencies**: when the main target is a -generic parameter (e.g. `CanCalculateArea: HasScalarType`), individual shape types need not -implement shared capabilities — the common context supplies the shared abstract type, value-level -injection (a global scale factor via a getter), and lazy per-context provider binding, so two apps -can wire the same shape to different providers. - ---- - -# Error handling - -CGP makes the error type abstract so generic code can fail without naming a concrete error. -`HasErrorType` (an abstract-type component, `type Error: Debug`) gives a context one shared -error type; `CanRaiseError` constructs it from a concrete source error -(`Context::raise_error(source)`); `CanWrapError` attaches detail. Both build on -`HasErrorType` and are associated-function (no `self`) components that dispatch per source/detail -type. An error-aware trait imports that error type with `#[use_type(HasErrorType.Error)]`, so it -names the error as the bare `Error` instead of writing `: HasErrorType` and `Self::Error` by hand: - -```rust -#[cgp_component(Loader)] -#[use_type(HasErrorType.Error)] -pub trait CanLoad { - fn load(&self, path: &str) -> Result; -} - -#[cgp_impl(new LoadOrFail)] -#[uses(CanRaiseError)] -#[use_type(HasErrorType.Error)] -impl Loader { - fn load(&self, path: &str) -> Result { - if path.is_empty() { - return Err(Self::raise_error("empty path".to_owned())); - } - Ok(format!("contents of {path}")) - } -} -``` - -A context wires its error type and the raise/wrap behavior. The backend providers — `RaiseFrom` -(convert via `From`), `ReturnError`, `RaiseInfallible`, `PanicOnError`, `DebugError`/`DisplayError` -(format into a `String` and forward), `DiscardDetail` — plug in per source type, modern-style with -`open`: - -```rust -delegate_components! { - App { - open ErrorRaiserComponent; - - ErrorTypeProviderComponent: UseType, - @ErrorRaiserComponent.String: RaiseFrom, - @ErrorRaiserComponent.ParseError: DebugError, - } -} -``` - -**Imports:** `HasErrorType`, `CanRaiseError`, and `CanWrapError` come from the prelude, but the -wiring keys (`ErrorTypeProviderComponent`, `ErrorRaiserComponent`, `ErrorWrapperComponent`) live -under `cgp::core::error`, and the backend providers (`RaiseFrom`, `DebugError`, …) under -`cgp::extra::error` — import the specific names you wire. Standalone backends (`cgp-error-anyhow`, -`cgp-error-eyre`, `cgp-error-std`) provide ready error types. - ---- - -# Handlers: the computation family - -CGP models computation as a family of components along three axes — synchronous vs. async, -infallible vs. fallible, and input-taking vs. input-free: - -- **`Computer` / `CanCompute`** — a synchronous, infallible transform `compute(&self, PhantomData, input) -> Output`. By-reference (`ComputerRef`) and async (`AsyncComputer`) variants exist. -- **`TryComputer` / `CanTryCompute`** — the fallible computer. -- **`Producer` / `CanProduce`** — input-free production (only a context and a `Code` tag). -- **`Handler` / `CanHandle`** — the general **async, fallible, error-aware** computation; the workhorse for I/O and pipelines. It supertraits `HasErrorType`. -- **`CanRun` / `CanSendRun`** — task runners. - -Any CGP trait with async methods — a handler, a runner, or one you define — declares them under the -`#[async_trait]` attribute, which rewrites each `async fn` to `-> impl Future` (the lint-clean, -allocation-free form). The generated future carries no `Send` bound, so spawning it on a -work-stealing executor needs the `Send`-recovery pattern in [handlers](references/handlers.md). - -`#[cgp_computer]` and `#[cgp_producer]` define a `Computer`/`Producer` provider from a function. -Providers compose through combinators: `PipeHandlers` chains handlers -left-to-right, `ComposeHandlers` nests them, `ReturnInput` passes input through, and the `Promote*` -adapters lift a simpler handler (e.g. a sync `Computer`) into a more capable one (an async -`Handler`). For example, a context wires a pipeline of field-reading computers: - -```rust -delegate_components! { - MyContext { - ComputerComponent: - PipeHandlers, - Add, - Multiply, - ]>, - } -} -// context.compute(PhantomData::<()>, 5) runs ((5*foo)+bar)*baz -``` - -Dispatch routes an extensible-data input to per-variant handlers: `#[cgp_auto_dispatch]` generates a -handler from a trait, and the combinators `MatchWithHandlers` / `MatchWithValueHandlers` / -`ExtractFieldAndHandle` match an enum's variants to sub-handlers, proving exhaustiveness without a -wildcard. Monadic handlers (`PipeMonadic`, `BindOk`, `BindErr`, the identity/ok/err monads) compose -handlers through a monad. The handler family is broad — read [handlers](references/handlers.md) for -the full set, including `Send`-bound recovery for async trait methods. - ---- - -# Extensible data - -CGP can build and read structs and enums generically, by their named fields and variants. The -`#[derive(HasFields)]` derive exposes a type's whole field list; `#[derive(CgpData)]` (and the -record/variant-specific `CgpRecord`/`CgpVariant`) derive the full extensible-data machinery: - -```rust -#[derive(CgpData)] -pub struct Person { - pub first_name: String, - pub last_name: String, -} -``` - -Records are built field-by-field through the builder family (`HasBuilder`, `BuildField`) — the -**extensible builder pattern** assembles a context from independent per-field outputs. Variants are -constructed with `FromVariant`, deconstructed with the `ExtractField` extractor family, and the -**extensible visitor pattern** handles each variant. The type-level spines underneath are the -product list (`Product![A, B, C]` over `Cons`/`Nil`) for records and the sum list (`Sum![A, B]` over -`Either`/`Void`) for variants. Structural **casts** convert between shapes — `CanUpcast` widens a -smaller enum into a larger one, `CanDowncast` narrows, `CanBuildFrom` rebuilds a record from a -superset. Dispatching extensible-data inputs to handlers uses the dispatch combinators above. - ---- - -# Namespaces - -Namespaces are reusable, inheritable wiring tables (a preset mechanism) that keep top-level wiring -short as component counts grow. `cgp_namespace! { new MyNs: ParentNs { … } }` defines a namespace -(optionally inheriting a parent after the colon); a context then **joins** it inside -`delegate_components!` with a `namespace MyNs;` statement, after which every lookup it does not wire -directly forwards through the namespace, so any direct entry overrides just that key. A component -**registers into** a namespace with the `#[prefix(@path in MyNs)]` attribute on its `#[cgp_component]` -trait, and a `#[cgp_impl]` provider registers as a per-type default with `#[default_impl(T in DefaultImpls1)]`, -which a context pulls in with a `for in Table { … }` loop. - -The underlying mechanism is the `RedirectLookup` provider, which re-routes a component lookup along a -type-level `Path!`; the `open` statement above is a lightweight special case of it. `DefaultNamespace` -resolves a default provider when a context does not override one. Read -[namespaces](references/namespaces.md) for the preset and inheritance syntax. - ---- - -# Type-level primitives: a decoder ring - -CGP encodes lists, strings, and numbers as types. You mostly use the sugared macros and only need to -*recognize* the expanded forms in errors: - -- **`Symbol!("name")`** — a type-level string (field-name tag). Expands to `Symbol<4, Chars<'n', Chars<'a', Chars<'m', Chars<'e', Nil>>>>`. The leading length works around missing const-generics. -- **`Product![A, B, C]`** — a type-level list. Expands to `Cons>>`. `product![…]` is the value-level form. Used for field lists and handler pipelines. -- **`Sum![A, B]`** — a type-level sum (the dual of `Product!`), over the `Either`/`Void` spine. Used for enum variant lists. -- **`Index`** — a type-level natural number, tags tuple-struct fields. -- **`Field`** — a value paired with its type-level name tag. -- **`Path!`** / `PathCons` — a type-level path, used by namespaces and `RedirectLookup`. -- **`Life<'a>`** — a lifetime lifted into a type, used when a component has lifetime parameters. -- **`MRef`** — an owned-or-borrowed value. - -Prefer the sugar (`Symbol!`, `Product!`) and the readable names (`Cons`/`Nil`) in anything you write. - ---- - -# Sub-skills: load the one that owns your task - -The primer gave you the shape of every construct; each sub-skill below is the ground truth for one -area — the exact grammar, the full expansion, the corner cases, and worked examples. **Loading the -relevant sub-skill is not optional polish; it is the step that turns a plausible guess into correct -code.** Every entry names what it adds beyond this primer *and* what you would be guessing at without -it, so you can see the risk of skipping it. Load a sub-skill whenever your task touches its area — -reading it, writing it, reviewing it, or debugging an error that mentions it — and re-load it when -you move into an unfamiliar corner. When a task spans several areas, load each one; they cross-link, -and following those links is expected, not a detour. - -Two of the sub-skills are cross-cutting rather than construct-specific, and one of them applies to almost every task. Start with **[references/macro-grammar.md](references/macro-grammar.md)** for any task that writes, edits, or debugs CGP syntax: it is the single reference for the formal grammar of every macro, the invariant each expansion preserves, and a decoder for the compiler errors CGP produces. *Without it* you are guessing which attribute forms parse, what a macro emits, and what an `IsProviderFor` or `DelegateComponent` error is actually telling you. Reach for **[references/modern-idioms.md](references/modern-idioms.md)** whenever you read or modernize existing CGP: it maps every legacy, explicit form to the modern idiom you should prefer, both to write vanilla-looking code and to decode the inside-out provider impls, hand-written `where` bounds, `Self::Type` paths, and `UseDelegate` tables you meet in older code. *Without it* you will either propagate outdated syntax or fail to recognize that legacy code and modern code mean the same thing. - -The remaining sub-skills each own one construct family: - -- **[references/components.md](references/components.md)** — `#[cgp_component]` and the full expansion (consumer/provider traits, the two blanket impls, the `…Component` marker), why `IsProviderFor` exists, and the three provider-writing macros (`#[cgp_impl]`, `#[cgp_provider]`, `#[cgp_new_provider]`). *Without it* you will misjudge what `self`/`Self` mean inside a provider and how the blanket impls route a call. Load it before writing any component or provider. -- **[references/wiring.md](references/wiring.md)** — `DelegateComponent`, every `delegate_components!` form (arrays, `new`, generic tables), `open` per-type dispatch, direct consumer-trait impls, `UseContext` and its circular-dependency trap, the legacy `UseDelegate` tables, and the other providers you see in tables (`WithProvider` and its `WithField`/`WithType`/`WithContext` aliases, `UseDefault`). *Without it* you will not know when a hand-written impl collides with the table, why `UseContext` overflows, or what a `WithField<…>` entry means. Load it before wiring any context. -- **[references/checking.md](references/checking.md)** — why wiring is lazy, how check traits and `CanUseComponent` force readable errors, every `check_components!` / `delegate_and_check_components!` option (`#[check_trait]`, `#[check_providers]`, `#[check_params]`, `#[skip_check]`), and a debugging playbook. *Without it* you cannot localize a broken wiring or read the error it throws. Load it whenever a wiring fails to compile. -- **[references/error-extraction.md](references/error-extraction.md)** — the **fallback for when `cargo-cgp` is not available, or leaves an error largely unrewritten** (see [Tooling](#tooling-use-cargo-cgp-for-readable-errors) first — `cargo-cgp`'s `[CGP-Exxx]` headline and root-cause tree already are the compact summary this sub-skill produces, so reach for the sub-skill only when the tool is absent or passes the error through). It covers how to reduce a long raw CGP compile error to a compact, root-cause-first summary, the hidden-versus-surfaced distinction that decides whether the root cause is even present in the output, how to confirm a suspected cause by grepping for one signature line, and how to delegate the reading to a sub-agent so a wall of generated-type errors does not consume your context. *Without it* you will read a cascade inline, chase a cause a hidden error does not contain, or hand back raw output instead of the few facts that matter. -- **[references/functions-and-getters.md](references/functions-and-getters.md)** — `HasField`/`#[derive(HasField)]`, `#[cgp_fn]`, `#[implicit]` and its access rules, `#[uses]`/`#[extend]`/`#[extend_where]`/`#[impl_generics]`, the getters `#[cgp_auto_getter]`/`#[cgp_getter]`/`UseField`/`WithField`, and `ChainGetters` for nested-context fields. *Without it* you will reach for a getter trait where an implicit argument is idiomatic, or misapply the `.clone()`/`.as_str()`/`&mut` field-access rules. Load it for the ergonomic day-to-day surface. -- **[references/abstract-types.md](references/abstract-types.md)** — `#[cgp_type]`, the built-in `HasType`/`TypeProvider`, wiring with `UseType` (and `UseDelegatedType` for table-chosen types), importing types with the `#[use_type]` attribute (distinct from the `UseType` provider), and the `WithType`/`WithDelegatedType` adapters. *Without it* you will confuse the provider and the attribute and write `Self::` paths by hand. Load it for any associated-type abstraction. -- **[references/higher-order-providers.md](references/higher-order-providers.md)** — providers parameterized by other providers, the stray `` on the inner bound, `#[use_provider]`, `UseContext` defaults, generic-parameter components, and cross-context dependencies. *Without it* you will call the inner provider as a method instead of `Provider::method(self)` and misplace the context slot. Load it before composing providers. -- **[references/error-handling.md](references/error-handling.md)** — `HasErrorType`, `CanRaiseError`/`CanWrapError`, the backend providers (`RaiseFrom`, `DebugError`, …), and — critically — which names come from the prelude versus `cgp::core::error` / `cgp::extra::error`. *Without it* you will fail to import the wiring keys and backends. Load it for any fallible CGP code. -- **[references/handlers.md](references/handlers.md)** — the `Computer`/`TryComputer`/`Producer`/`Handler`/runner family across its three axes, `#[cgp_computer]`/`#[cgp_producer]`/`#[cgp_auto_dispatch]`, the combinators (`PipeHandlers`, `Promote*`, dispatch matchers), monadic handlers, the `HasRuntime`/`HasRuntimeType` runtime components, and the `Send`-recovery pattern. *Without it* you will pick the wrong family member or miswire a pipeline. Load it for computation and I/O pipelines. -- **[references/extensible-data.md](references/extensible-data.md)** — the `CgpData`/`CgpRecord`/`CgpVariant` derives, the builder and extractor families (with the optional/defaulted-field extension), `Product!`/`Sum!` spines and their `AppendProduct`/`ConcatProduct`/`MapFields` algebra, structural casts (`CanUpcast`/`CanDowncast`/`CanBuildFrom`), and the builder/visitor patterns. *Without it* you will miss the compile-time exhaustiveness guarantees and the single-payload variant rule. Load it for generic struct/enum manipulation. -- **[references/namespaces.md](references/namespaces.md)** — `cgp_namespace!`, the `namespace`/`for … in` statements that join a namespace, the `#[prefix]`/`#[default_impl]` attributes that register into one, `RedirectLookup`, `Path!`, and the `DefaultNamespace` family. *Without it* you cannot read or write preset/inheritance wiring. Load it whenever wiring is grouped or inherited. -- **[references/type-level-primitives.md](references/type-level-primitives.md)** — `Symbol!`/`Chars`, `Product!`/`Cons`/`Nil`, `Sum!`/`Either`/`Void`, `Index`, `Field`, `Path!`/`PathCons`, `Life`, `MRef`, and the `StaticFormat` recovery traits. *Without it* you cannot decode the long nested types in error messages and expansions. Load it as the decoder ring. -- **[references/modularity-hierarchy.md](references/modularity-hierarchy.md)** — the five-level spectrum from a plain blanket trait to per-provider wiring, and which coherence rule each level escapes. *Without it* you will reach for more CGP machinery than a problem needs. Load it when deciding *how much* CGP to apply. - -## Exhaustive online reference - -For the exact macro expansion of any construct, every accepted syntax form, corner cases, or the -implementing source, consult the online knowledge base at -**https://github.com/contextgeneric/cgp/tree/main/docs** — its `reference/`, `concepts/`, and -`examples/` directories are the authoritative, exhaustive record. Fetch the relevant page when a -detail is not covered here; do not assume a local copy exists, since this skill is deployed on its -own. - ---- - -# Instructions for explaining CGP to users - -Assume by default that the user has only basic Rust experience and is new to CGP, but do not -over-explain: when code merely uses CGP concepts, write or modify it without lecturing, and add -explanation only when asked. When you do explain, assume unfamiliarity with advanced Rust (generics, -traits, blanket impls, coherence) and with functional/type-level programming — describe type-level -tables, lists, and strings through familiar analogies such as a map or a lookup table, and expand on -advanced Rust as needed. Keep the simplified picture front and center: present wiring as choosing a -table entry, and keep `IsProviderFor`, `DelegateComponent`, and generated blanket impls out of the -explanation unless the user is asking specifically about the internals. One caveat when you reach -for an analogy: the "table lookup" is resolved at compile time and compiles down to direct static -calls, so if you use a runtime-flavored analogy like a vtable, say explicitly that — unlike a real -vtable — CGP's resolution is static and zero-cost, with no runtime table or dynamic dispatch. Never -leave a reader thinking CGP wiring has runtime lookup overhead. - -When asked to explain a specific piece of code, look up the definitions it depends on before -answering. To explain a `delegate_components!` entry, find the consumer and provider traits behind -the component key and the body of the provider it maps to. To explain a provider, read its own -definition and the definitions of every capability in its `where` clause. To explain how a context -implements something, follow its wiring to see which providers are chosen and trace a method call -through them — for instance, if `NameGetterComponent` is wired to `UseField`, -then a `self.name()` call inside another provider returns the context's `first_name` field. diff --git a/docs/skills/cgp/references/abstract-types.md b/docs/skills/cgp/references/abstract-types.md deleted file mode 100644 index 603800cb..00000000 --- a/docs/skills/cgp/references/abstract-types.md +++ /dev/null @@ -1,162 +0,0 @@ -# Abstract types - -An abstract type is a CGP trait carrying a single associated type that generic code names as `Self::Foo` without committing to a concrete type, leaving the concrete choice to wiring so it can differ from one context to another. - -## The idea - -An abstract type lets generic code name a type it does not fix. Instead of hard-coding `f64` or `String`, a trait declares one associated type — `trait HasNameType { type Name; }` — and code written against it refers to `Self::Name`, leaving the actual type open. The trait is the abstraction; the associated type is the slot a context fills in. This is the type-level analogue of an impl-side dependency: just as a getter lets a context supply a *value* a provider needs, an abstract-type trait lets a context supply a *type* a provider builds on. - -The payoff is the same one CGP gives for behavior. A provider written in terms of `Self::Name` works unchanged whether a context chooses `String`, `&'static str`, or a custom name type, and two contexts can make different choices from the same generic code. Under the hood an abstract type is nothing more than an ordinary Rust trait with one associated type — generic functions constrain `Context: HasNameType` and use `Context::Name` exactly as they would any associated type. The CGP machinery only makes declaring and wiring these traits cheap. - -## Direct implementation: it's just a trait - -Because an abstract type is a vanilla associated-type trait, the most transparent way to bind it is to implement it directly on a concrete context. Given the trait, a context names the concrete type in a plain `impl`: - -```rust -#[cgp_type] -pub trait HasNameType { - type Name; -} - -pub struct Person; - -impl HasNameType for Person { - type Name = String; -} -``` - -This makes `Person::Name` resolve to `String`, and any generic code constrained on `HasNameType` sees that choice. The direct form is the right mental model for newcomers — there is no hidden machinery, just an associated type pinned to a concrete one — and it is barely longer than the wired form shown below. The rest of this reference explains the `#[cgp_type]` macro and the wiring conveniences that make the choice swappable through a delegation table instead. - -## Making a type swappable with `#[cgp_type]` - -The `#[cgp_type]` macro turns an abstract-type trait into a full component, so the concrete type can be chosen through wiring rather than a hand-written impl. It is the abstract-type specialization of `#[cgp_component]`: applied to a trait with exactly one associated type and no methods, it produces everything `#[cgp_component]` would — the consumer trait, the provider trait, the blanket impls, the `…Component` marker — but each impl forwards the *associated type* rather than a method. - -```rust -#[cgp_type] -pub trait HasNameType { - type Name; -} -``` - -The default provider name is keyed off the *associated type* name, not the trait name, with a `TypeProvider` suffix. So `type Name;` yields the provider `NameTypeProvider` and the component marker `NameTypeProviderComponent`. A bound on the associated type — `type Name: Clone;` — is carried everywhere the type appears in the expansion and enforced on whatever concrete type a context chooses. You can override the derived provider name by passing one, exactly as with `#[cgp_component]`: `#[cgp_type(ProvideName)]`. - -The construct that distinguishes `#[cgp_type]` from a plain component is an extra blanket impl it generates for the `UseType` provider, described next, which is what lets a context pick a concrete type without writing a provider of its own. - -## Wiring a concrete type with `UseType` - -A context binds an abstract type to a concrete one by wiring its provider component to `UseType`. Because every abstract-type provider has the same trivial shape — "the associated type *is* this concrete type" — `#[cgp_type]` generates that shape once as a blanket impl of the provider trait for `UseType`, setting the associated type to the generic parameter. The context then names the concrete type directly in its delegation table: - -```rust -delegate_components! { - Person { - NameTypeProviderComponent: UseType, - } -} -``` - -Wiring `NameTypeProviderComponent` to `UseType` makes `Person` implement `HasNameType` with `Name = String`, with no bespoke provider, and any bound on the associated type is checked against `String` at the wiring site. `UseType` is a zero-sized marker struct carrying no runtime value — it exists only to be named in a delegation table, never constructed. This is the type-level mirror of how `UseField` supplies a value-level getter (see [functions and getters](functions-and-getters.md)). - -The blanket impl that `#[cgp_type]` generates for the example above is: - -```rust -impl NameTypeProvider<__Context__> for UseType { - type Name = Name; -} -``` - -This says `UseType` is a provider that supplies `T` as the abstract type, for any context. If the associated type carried a bound, that bound would be copied into the impl's `where` clause so the concrete type must satisfy it. - -A word of caution on the name: the `UseType` *provider struct* shown here is a different construct from the `#[use_type]` *attribute* covered below. The provider wires a concrete type *into* a context; the attribute imports an abstract type *into* a definition and rewrites bare mentions of it. They share a name because both center on abstract types, but they live in different places and do different jobs. - -## The built-in `HasType` / `TypeProvider` component - -Underneath every named abstract type sits CGP's single built-in abstract-type component, `HasType`. It is tag-indexed: `HasType` is the consumer trait, `TypeProvider` is its provider trait, and a context can carry many distinct abstract types — one per `Tag` — resolving each through wiring. - -```rust -#[cgp_component(TypeProvider)] -pub trait HasType { - type Type; -} -``` - -Every `#[cgp_type]` component you define is wired on top of this substrate: the macro generates an internal `WithProvider` impl that adapts a `TypeProvider` into the named component, so the same `UseType` marker satisfies both the built-in `HasType` and any user-defined abstract type at once. In practice you rarely name `HasType` directly — you define a readable `HasNameType` with `#[cgp_type]` and get `Self::Name` and its own provider, all resolving down to this `HasType` machinery. The takeaway is that `UseType` is itself a `TypeProvider`, which is why one marker serves every abstract type. When you do want to name that adapter explicitly, its alias is `WithType` (the `WithProvider` family from [wiring](wiring.md)). - -## Choosing the concrete type from a table with `UseDelegatedType` - -`UseDelegatedType` is the type-level analogue of the `UseDelegate` dispatcher: where `UseType` binds an abstract type to one fixed `T`, `UseDelegatedType` looks the concrete type up in an inner `DelegateComponent` table keyed by the type tag, so one provider can answer several abstract-type components at once or route each tag to a type chosen elsewhere. It reads an entry out of the same kind of table `UseDelegate` reads, but yields a *type* rather than a method — the abstract-type mirror of the per-value behavioral dispatch in [wiring](wiring.md). Its `WithProvider` alias is `WithDelegatedType`. Reach for it only when a bundle of related types must be decided together; for a single concrete type per component, `UseType` is simpler. - -## Abstract type as a getter return type - -When an abstract type's only role is to be the return type of a getter, you can declare it inline with `#[cgp_auto_getter]` rather than defining a separate `#[cgp_type]` trait. The getter trait carries the associated type locally, and the field's type is inferred from it: - -```rust -#[cgp_auto_getter] -pub trait HasName { - type Name; - - fn name(&self) -> &Self::Name; -} -``` - -A context implementing this through its field wiring supplies both the concrete `Name` and the value. This keeps a one-off abstract type local to the getter that uses it instead of promoting it to a shared, wired component. See [functions and getters](functions-and-getters.md) for how `#[cgp_auto_getter]` derives the getter from a field. - -## Importing an abstract type with `#[use_type]` - -The strongly recommended way to *refer to* an abstract type from another definition is the `#[use_type]` attribute. A provider or component often needs a type that lives on a different trait — a `Scalar` from `HasScalarType`, an `Error` from `HasErrorType` — and Rust requires every mention to be written in fully-qualified form, `::Scalar`, because a bare `Scalar` is not a type the compiler knows. Writing that prefix on every occurrence is verbose and easy to get wrong. - -`#[use_type]` lets you write the bare identifier everywhere and have the macro expand it. You declare the import once alongside `#[cgp_fn]`, `#[cgp_impl]`, or `#[cgp_component]` as `#[use_type(Trait.AssocType)]` — a `.` (not `::`) separates the trait from the associated type — and the macro rewrites each standalone `Scalar` into `::Scalar` while also adding `HasScalarType` as a supertrait (for `#[cgp_component]`) or a `where`-clause bound (for `#[cgp_impl]` and `#[cgp_fn]`). The `.` separator keeps the trait unambiguous even when it is a full path or carries generic arguments (`errors::HasErrorType.Error`, `HasFooType.Foo`). Consider a `rectangle_area` function that multiplies two implicit fields: - -```rust -pub trait HasScalarType { - type Scalar: Clone + Mul; -} - -#[cgp_fn] -#[use_type(HasScalarType.Scalar)] -fn rectangle_area( - &self, - #[implicit] width: Scalar, - #[implicit] height: Scalar, -) -> Scalar { - width * height -} -``` - -The macro rewrites every bare `Scalar` to the qualified path and adds the supertrait/bound, so the effective trait and impl read: - -```rust -pub trait RectangleArea: HasScalarType { - fn rectangle_area(&self) -> ::Scalar; -} - -impl RectangleArea for Context -where - Self: HasField::Scalar> - + HasField::Scalar>, - Self: HasScalarType, -{ - fn rectangle_area(&self) -> ::Scalar { - let width: ::Scalar = - self.get_field(PhantomData::).clone(); - let height: ::Scalar = - self.get_field(PhantomData::).clone(); - width * height - } -} -``` - -The substitution is purely textual at the type level — it matches single-segment, argument-free type paths whose identifier equals the imported name — so a bare `Scalar` in the return type, an implicit-argument annotation, or a `let` binding inside the body is all rewritten the same way. Beyond saving keystrokes, the always-qualified rewrite removes the ambiguity the bare form cannot express, which is why this is the default way to import abstract types in all three macros. - -The attribute has a few richer forms worth knowing. A trailing `in Context` clause changes the rewrite target from `Self` to a named type, which imports a *foreign* abstract type from a generic parameter: `#[use_type(HasScalarType.Scalar in Types)]` rewrites `Scalar` to `::Scalar` and adds `Types: HasScalarType` as a `where` bound rather than a supertrait. That bound lands on the generated impl *and*, on `#[cgp_fn]`/`#[cgp_component]`, on the generated trait itself, so a plain unbounded `` parameter is enough — you do not restate `Types: HasScalarType` by hand. The `in Context` clause may also point at another abstract type imported in the same attribute, chaining through several hops (`HasTypes.Types, HasScalarType.Scalar in Types` yields the two-hop `<::Types as HasScalarType>::Scalar`). A braced list imports several types from one trait, each optionally renamed with `as` or constrained with `=`: `#[use_type(HasScalarType.{Scalar = f64})]` both imports `Scalar` and emits `Self: HasScalarType`, pinning it. This equality form is the modern replacement for a hand-written `where Self: HasScalarType` clause on a `#[cgp_impl]` or `#[cgp_fn]` — prefer moving such a pin into the attribute rather than leaving it as an explicit `where`. The right-hand side of `=` may name another imported alias to *unify* two abstract types: `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`, tying the two together. To import types from *several* traits, separate the trait paths with commas inside one attribute — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — and prefer that combined form over stacking one `#[use_type]` attribute per trait, since it reads as a single import list; stacked attributes behave identically but are for when a real reason calls for it. The `= ...` equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side equality constraint it produces; it belongs on `#[cgp_fn]` and `#[cgp_impl]`. Two imports may not resolve to the same identifier or alias — across specs or within one braced list — since the substitution could then match only one; a collision is a compile error on every host macro. - -## Sharing one type across contexts - -The value of an abstract type compounds when several pieces of generic code share it. Because the type lives on a trait the context implements, every provider and trait that needs a `Scalar` refers to the *same* `Self::Scalar`, so a context fixes the choice once and all of them agree. This is sharpest when a trait's main subject is a generic parameter rather than the context itself — a `CanCalculateAreaOfShape` implemented by one context for many shapes. The shapes carry no scalar type of their own; the shared context supplies a single `Scalar` through its `HasScalarType` wiring, and switching `UseType` to `UseType` changes the scalar for every shape at once. The same arrangement is how CGP shares one error type across an application: `HasErrorType` is itself defined with `#[cgp_type]`, and every fallible provider refers to the same `Self::Error` (see [error handling](error-handling.md)). - -## Related references - -Abstract types are wired into a context with [components](components.md) and consumed by [functions and getters](functions-and-getters.md). `UseType` is one of the higher-order providers described in [higher-order providers](higher-order-providers.md), and the shared error type pattern is covered in [error handling](error-handling.md). - -## Further reference - -Online docs: [`#[cgp_type]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_type.md), [`HasType`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/has_type.md), [`UseType` provider](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/use_type.md), [`#[use_type]` attribute](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/use_type.md). diff --git a/docs/skills/cgp/references/checking.md b/docs/skills/cgp/references/checking.md deleted file mode 100644 index 086f045d..00000000 --- a/docs/skills/cgp/references/checking.md +++ /dev/null @@ -1,207 +0,0 @@ -# Checking wiring - -How to verify at compile time that a context's [wiring](wiring.md) is complete, using check traits and the `check_components!` / `delegate_and_check_components!` macros to turn confusing use-site errors into precise ones at the wiring site. - -## Why wiring is lazy - -CGP wiring is lazy: recording that a context delegates a [component](components.md) to a provider does not verify that the provider can actually satisfy that component for that context. When you write a `DelegateComponent` entry mapping `GreeterComponent` to `GreetHello`, the type system stores "this key points to this provider" as an associated type and asks no further questions. Whether the provider's own `where` bounds — its impl-side dependencies — hold for this particular context is simply not checked at the point of delegation. The check is deferred until something downstream actually *uses* the component, which is the first moment the compiler is forced to evaluate the provider's bounds against the concrete context. - -This laziness is what makes CGP composable, but it has a cost: a context can look fully wired and still be broken. Every entry compiles, the struct compiles, the whole module compiles — and then the first call to a consumer trait method fails, often far from the wiring that caused it. A missing field, a missing abstract type, an unsatisfied transitive bound three providers deep: none of these surface where the mistake was made. - -Consider a greeter provider that depends on a `name` field, wired onto a context whose field is misnamed: - -```rust -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasName, -{ - fn greet(&self) { - println!("Hello, {}!", self.name()); - } -} - -#[derive(HasField)] -pub struct Person { - pub first_name: String, // mismatch: GreetHello needs `name` -} - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} -``` - -This whole block compiles. `GreetHello` needs `HasName`, `Person` has no `name` field, and nothing complains — until some distant `person.greet()` call fails to typecheck. - -## Why the resulting errors are poor - -When a lazily-wired context is finally used and a dependency is missing, the compiler reports the outermost unmet conclusion and hides the reasoning behind it. Asking the plain question "does `Person` implement `CanGreet`?" makes Rust answer with the last link in the chain — typically that `GreetHello` does not implement the provider trait for `Person` — without explaining *why* the provider's bounds were not met. The provider blanket impl is a competing candidate that suppresses the detailed diagnostic. The root cause, a single missing `name` getter, is buried beneath a conclusion that points at the provider rather than at the gap, and the surface error spells field names in their verbose type-level form (`Symbol<…, Chars<…>>`), making even that hard to read. - -The fix is to force the compiler to evaluate the provider's bounds *and report them in detail*, at a location you control — the wiring site — rather than wherever the component happens to be used first. - -## How check traits force readable errors - -A check trait is a dummy trait whose supertrait is the requirement being asserted; implementing it for a context with an empty body compiles only if that requirement holds. The hand-written form is plain Rust: - -```rust -trait CanUsePerson: CanGreet {} -impl CanUsePerson for Person {} -``` - -The `impl` block has nothing to prove on its own, so it succeeds exactly when `Person: CanGreet` holds and fails otherwise. Placed next to the wiring, it converts a latent gap into an immediate compile error at a known line. But asserting the *consumer* trait directly is not enough: it reproduces the same vague error as before, naming the provider rather than the missing dependency. The remedy is to route the assertion through `CanUseComponent` instead. - -`CanUseComponent` is satisfied only when the context both delegates the component and the delegated provider satisfies `IsProviderFor` for that context. The crucial property is that `IsProviderFor` carries the provider's *real* `where` bounds — the same impl-side dependencies the provider needs to implement its provider trait. Routing a check through `CanUseComponent` therefore forces the compiler to evaluate those bounds and, because they are stated explicitly through the marker, to report the specific one that failed. A missing `name` field surfaces as an unsatisfied `HasName`/`HasField` bound pointing at the context, not as a bare "provider not implemented." The two bounds also distinguish the two ways wiring goes wrong: failing the `DelegateComponent` bound means the component was never wired (add the delegation), while failing the `IsProviderFor` bound means it was wired to a provider whose dependencies are unmet (supply the missing dependency). You rarely name `CanUseComponent` directly — its job is to be the bound the check macros emit. - -## Generating checks with `check_components!` - -Rather than spell out check traits by hand, `check_components!` generates them from a short table of components to verify. Given a context and a list of components, it emits a marker trait aliasing `CanUseComponent` and one empty impl per component: - -```rust -check_components! { - Person { - GreeterComponent, - } -} -``` - -The generated impl compiles only if `Person: CanUseComponent`, which drags in `GreetHello`'s `IsProviderFor` bounds and reports the first that fails — here, the missing `name` field, pinpointed at the wiring site instead of at a future `person.greet()`. A successful build *is* the passing assertion; these checks have no runtime existence. - -The check trait is named `__Check{Context}` by default — `__CheckPerson` here. When two `check_components!` tables in the same module would collide on that name, override it with `#[check_trait(Name)]` on the table: - -```rust -check_components! { - #[check_trait(CheckPersonGreeting)] - Person { - GreeterComponent, - } -} -``` - -A component with generic parameters cannot be checked bare, because the check must name concrete parameters to have anything to verify. List them after a colon: a single parameter bare, multiple parameters grouped into a tuple, mirroring how the provider trait groups them in its `IsProviderFor` `Params` slot. Given an area calculator generic over a shape: - -```rust -#[cgp_component(AreaOfShapeCalculator)] -pub trait CanCalculateAreaOfShape { - fn area(&self, shape: &Shape) -> f64; -} - -check_components! { - MyApp { - AreaOfShapeCalculatorComponent: Rectangle, // one parameter - TransformCalculatorComponent: (Rectangle, f64), // two, as a tuple - } -} -``` - -Array syntax on either side of the colon expands to the cartesian product, so a set of components can be checked against a set of parameters in one line. A bracketed value checks one component against several parameter sets; a bracketed key checks several components against one set; bracketing both checks every combination: - -```rust -check_components! { - MyApp { - AreaOfShapeCalculatorComponent: [Rectangle, Circle], // one component, two shapes - } -} -``` - -This verifies `MyApp: CanCalculateAreaOfShape` and `MyApp: CanCalculateAreaOfShape` in one entry. - -## Checking providers directly with `#[check_providers(...)]` - -For [higher-order providers](higher-order-providers.md), checking the context as a whole tells you a layer is broken but not which one. The `#[check_providers(...)]` attribute changes what is checked: instead of asserting `CanUseComponent` on the context, it asserts `IsProviderFor` directly on each named provider, so each layer is verified on its own impl: - -```rust -check_components! { - #[check_trait(CheckScaledRectangleProviders)] - #[check_providers( - RectangleAreaCalculator, - ScaledAreaCalculator, - )] - ScaledRectangle { - AreaCalculatorComponent, - } -} -``` - -Because each provider is checked independently, a dependency missing only from the outer `ScaledAreaCalculator` wrapper errors on the wrapper's line alone, while one missing from the inner `RectangleAreaCalculator` errors on both lines — which narrows down where in a nested stack the gap lives. - -## Wiring and checking together with `delegate_and_check_components!` - -For basic wiring, and especially while getting started with CGP, `delegate_and_check_components!` removes the bookkeeping of keeping a standalone `check_components!` block in sync with the delegations. It fuses the two — wiring each entry exactly as `delegate_components!` would and deriving a check for each delegated key — so a simple context is proven the moment it is written and a newcomer cannot forget the check. (Its reach stops at that basic form, which is why advanced codebases keep the two macros separate; see the recommendation at the end of this section.) - -```rust -#[derive(HasField)] -pub struct MyContext { - pub name: String, -} - -delegate_and_check_components! { - MyContext { - NameTypeProviderComponent: UseType, - NameGetterComponent: UseField, - } -} -``` - -If `MyContext` were missing the `name` field, the derived check on `NameGetterComponent` would fail to compile and report the missing bound, rather than letting the gap slip through to a later use. - -Its check trait is named `__CanUse{Context}` by default — deliberately distinct from `check_components!`'s `__Check{Context}` — so one of each macro can appear in the same module without a clash. As with `check_components!`, `#[check_trait(Name)]` overrides the derived name. - -Because the delegation half is generic over a component's parameters but the check half needs concrete ones, an entry for a component with generic parameters *requires* a `#[check_params(...)]` attribute supplying them, using the same single-versus-tuple convention: - -```rust -delegate_and_check_components! { - MyApp { - #[check_params(Rectangle, Circle)] - AreaOfShapeCalculatorComponent: - UseDelegate, - } -} -``` - -The nested `UseDelegate` table shown here is the legacy form of per-type dispatch; the modern equivalent opens the component with the `open` statement of `delegate_components!` (see [wiring](wiring.md)). The `#[check_params(...)]` requirement is the same either way — whichever wiring form supplies the dispatch entries, the check half still needs the concrete parameters spelled out. - -To wire an entry without checking it — for instance a higher-order delegation you verify separately with a `#[check_providers(...)]` block — mark it `#[skip_check]`. The two attributes are mutually exclusive on a given entry: - -```rust -delegate_and_check_components! { - ScaledRectangle { - AreaCalculatorComponent: - ScaledAreaCalculator, - - #[skip_check] - TransformCalculatorComponent: - ComplexTransform, // checked in a dedicated check_components! block - } -} -``` - -Where `delegate_and_check_components!` fits is narrower than its convenience suggests: it is the beginner-friendly, basic-wiring form, not the default for advanced code. Its value is that a newcomer cannot forget to write a separate `check_components!` and then be tripped by the confusing lazy-wiring errors that follow; it derives a check for each plain `Component: Provider` entry so getting-started code is checked by construction. But the derivation only understands that basic delegation form. It cannot easily generate check traits for the advanced mappings — generic-parameter dispatch through the `open` statement and `@`-path keys, namespace joins, and per-layer higher-order checks — because those need concrete parameters or providers the fused derivation cannot infer from the delegation alone. So in larger, more advanced codebases, keep `delegate_components!` and `check_components!` separate: the standalone `check_components!` block is where `#[check_providers(...)]`, concrete parameters for generic keys, and checks over opened or namespaced wiring all live. The rule that does not bend is that a context's wiring is checked *somehow*; `delegate_and_check_components!` is simply the training-wheels way to guarantee that for simple contexts, and the two separate macros are the way that scales. - -There is also a case where `delegate_and_check_components!` is not merely unnecessary but outright *wrong*: an **aggregate provider**. A `delegate_components!` table need not describe a context at all — a `new SomeComponents { … }` table (see [wiring](wiring.md)) defines a zero-sized provider that dispatches each component to a sub-provider, which other contexts then delegate to as a reusable bundle. Such an aggregate provider is always wired with plain `delegate_components!`, never the checked variant, because the check asserts that the target can *use* each component as a context — `Target: CanUseComponent` — and an aggregate provider is never its own context. It has no fields and never implements a provider trait with itself in the context position, so the derived `CanUseComponent` assertion could not hold and the macro would report spurious failures. An aggregate provider is verified indirectly, when a real context that delegates to it is checked, or directly with a `#[check_providers(...)]` block that names the aggregate and asserts `IsProviderFor` on it for a real context — the provider-side check, not the context-side one. - -## Debugging an unsatisfied check - -When a check fails, the error names the unmet bound — read it as a thread to pull rather than a final verdict. The reported bound is the first impl-side dependency the compiler could not satisfy; walk the transitive dependencies from there. If `GreetHello` requires `HasName`, the error names `HasName` on the context, and the fix is to supply that field or wire the getter component. A bound several providers deep names the innermost requirement, so trace from the failing component through each provider it delegates into. - -When a large `delegate_and_check_components!` table reports a tangle of errors, narrow it down by adding the suspect component to a separate `check_components!` block on its own. Checking one component in isolation, with its parameters spelled out, strips away the noise from the other entries and forces the compiler to report just that component's unmet dependency. For a higher-order stack, switch to `#[check_providers(...)]` to see which layer fails on its own line. - -Finally, remember that not every unsatisfied bound is a CGP component. A check trait only verifies wiring routed through `CanUseComponent` — it cannot prove a plain trait or a blanket-impl bound that a provider also depends on. If the error names a trait that has no `…Component` marker and no entry in any delegation table, no amount of checking will surface it through the wiring; that bound must be satisfied by ordinary Rust means (an `impl`, a derive, a `where` clause on the context), and the check will pass only once it is. - -## Further reference - -Online docs: [`check_components.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/check_components.md), [`delegate_and_check_components.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/delegate_and_check_components.md), and the conceptual overview [`concepts/check-traits.md`](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/check-traits.md). diff --git a/docs/skills/cgp/references/components.md b/docs/skills/cgp/references/components.md deleted file mode 100644 index 17a2832b..00000000 --- a/docs/skills/cgp/references/components.md +++ /dev/null @@ -1,189 +0,0 @@ -# Components - -A CGP **component** is the bundle that `#[cgp_component]` generates from one trait so that *using* a capability and *implementing* it become separate, swappable things — this is the central reference; read it first. - -## What a component is - -A component is a single trait definition compiled into a small machine: a **consumer trait** that callers invoke, a **provider trait** that implementations target, a `…Component` marker key, and the blanket impls that connect them. An ordinary Rust trait conflates using a capability with implementing it — the type you call `.area()` on is the same type that supplies the `area` body — and Rust's coherence rules then allow only one implementation per type. A component breaks that conflation, which is what lets many independent implementations of the same capability coexist and lets you implement a capability for a type you do not own. - -The split is produced by `#[cgp_component]`, applied to an ordinary trait. Throughout this file the running examples are the greeting component (`CanGreet` / `Greeter` / `GreetHello`, on a `Person`) and the area component (`CanCalculateArea` / `AreaCalculator` / `RectangleArea`, on a `Rectangle`). Assume `use cgp::prelude::*;` in every snippet; the CGP version is v0.8.0. - -## What `#[cgp_component(Greeter)]` generates - -Applying the macro to a consumer trait produces five items: the consumer trait, the provider trait, two blanket impls, and the marker struct. Start from the trait, naming the provider trait in the attribute argument: - -```rust -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} -``` - -The first item is the **consumer trait**, emitted unchanged. This is the self-style trait callers use (`CanDoX`), so a caller writes `person.greet()` exactly as with any trait: - -```rust -pub trait CanGreet { - fn greet(&self); -} -``` - -The second item is the **provider trait**, the same interface with `Self` moved out into an explicit leading `Context` type parameter and every `self`/`Self` rewritten to `context`/`Context`. It is named in noun form (`SomethingDoer`, or `…Provider` when no noun fits), and it carries an `IsProviderFor` supertrait that captures the component, the context, and a `Params` tuple of any extra type parameters — `()` when there are none: - -```rust -pub trait Greeter: - IsProviderFor -{ - fn greet(context: &Context); -} -``` - -A provider trait is implemented not for the context but for a dedicated zero-sized **provider** struct — a type-level marker that is never instantiated and carries no runtime value. Because a provider implements `Greeter` for *its own* struct over a generic `Context`, the orphan and overlap rules never bite, so `GreetHello`, `GreetGoodbye`, and any number of further providers for the same component can all exist at once. See [bypassing coherence](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/coherence.md) (online) for why moving `Self` to a parameter is what sidesteps the rules, and [modularity hierarchy](modularity-hierarchy.md) for the spectrum of how far to take the split. - -## How the two traits connect - -Two generated blanket impls bridge the consumer and provider sides, and together they make `person.greet()` resolve to a chosen provider without the caller naming it. Read them as the wiring machinery; you never write them. - -The first is the **consumer blanket impl**, which says that any context implementing the provider trait *for itself* automatically gets the consumer trait. It forwards `context.greet()` to `Context::greet(self)`: - -```rust -impl CanGreet for Context -where - Context: Greeter, -{ - fn greet(&self) { - Context::greet(self) - } -} -``` - -The second is the **provider blanket impl**, which lets any provider that delegates this component inherit the provider trait from whatever it delegates to. The delegation is a type-level table lookup through `DelegateComponent`, keyed on the `GreeterComponent` marker: - -```rust -impl Greeter for Provider -where - Provider: DelegateComponent - + IsProviderFor, - Provider::Delegate: Greeter, -{ - fn greet(context: &Context) { - Provider::Delegate::greet(context) - } -} -``` - -The fifth item is the **component marker**, a zero-sized key into those delegation tables: - -```rust -pub struct GreeterComponent; -``` - -These two examples use readable names for clarity; the emitted code uses reserved identifiers — `__Context__` for the context parameter (overridable) and `__Provider__` for the provider parameter — chosen so they never clash with a user's own type names. - -## Wiring is the table lookup that ties it all together - -**Wiring** is the step that supplies the delegation the provider blanket impl reads, by making a context into a type-level table whose entry for each component names the chosen provider. With the greeting component wired, the chain resolves end to end: `person.greet()` goes through the consumer impl to `Person` implementing the provider trait for itself, which goes through the provider impl to the table entry, landing on the selected provider's `greet`. Swapping the table entry is the only change needed to swap behavior; no caller is touched. - -```rust -#[derive(HasField)] -pub struct Person { - pub name: String, -} - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} -``` - -The full table grammar — including per-value dispatch and presets — lives in [wiring](wiring.md). This file shows only the single-entry form needed to make the resolution concrete. - -## Why `IsProviderFor` exists - -`IsProviderFor` is an empty marker trait that rides along on every provider trait as a supertrait, and its only job is to make a missing dependency produce a readable error. A provider lists what it needs from the context in a `where` clause; when that clause is unmet, the bare question "does this provider implement the provider trait?" yields only "trait not implemented", because the provider blanket impl is also a candidate and Rust suppresses its detailed reasoning whenever more than one impl could apply. - -`IsProviderFor` is the second, independent path that un-hides the real reason. The macros implement it for a provider under *exactly the same* `where` bounds as the provider trait, and because that impl is the only candidate (no competing blanket), Rust commits to it and prints the precise unsatisfied constraint. The practical translation a reader needs: an `IsProviderFor` not implemented error means the provider trait is not implemented, and the named bound is the missing dependency. The trait is generated and consumed entirely by the macros — you observe it in errors, you never write it. - -```rust -#[diagnostic::on_unimplemented( - note = "You need to add `#[cgp_provider({Component})]` on the impl block for CGP provider traits" -)] -pub trait IsProviderFor {} -``` - -## Writing providers - -A provider can be written at three levels of sugar over the same machinery, and `#[cgp_impl]` is the one to reach for. The lower forms exist for when the native provider-trait shape is wanted explicitly, and they are what `#[cgp_impl]` desugars to. - -The lowest form is `#[cgp_provider]`, applied to a provider-trait impl written directly on a provider struct. It passes the impl through unchanged and auto-generates the matching `IsProviderFor` impl from the same `where` clause, so the dependency set can never drift. The provider struct must already exist: - -```rust -pub struct RectangleArea; - -#[cgp_provider] -impl AreaCalculator for RectangleArea -where - Context: HasDimensions, -{ - fn area(context: &Context) -> f64 { - context.width() * context.height() - } -} -``` - -That expands to the impl above plus the empty marker impl carrying the same bound, which is what surfaces a missing `HasDimensions` as a named error: - -```rust -impl IsProviderFor for RectangleArea -where - Context: HasDimensions, -{} -``` - -The optional attribute argument overrides the component type, defaulting otherwise to the provider trait's name plus a `Component` suffix; pass it explicitly only when the trait name does not follow that convention. - -The middle form is `#[cgp_new_provider]`, identical to `#[cgp_provider]` but it also declares the provider struct, folding `pub struct RectangleArea;` into the same block. Use it when introducing a fresh provider so you need not write the struct separately; a generic provider yields a struct with a `PhantomData` field over its parameters. - -The preferred form is `#[cgp_impl]`, which lets you write the provider in **consumer-style syntax** — keeping `self`, `Self`, and the consumer trait's method signatures — and mechanically rewrites it into the provider-trait shape. The provider name moves into the attribute argument instead of the `Self` position; a leading `new` keyword declares the struct; and you may omit `for Context` entirely, letting the macro insert the context parameter for you: - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator -where - Self: HasDimensions, -{ - fn area(&self) -> f64 { - self.width() * self.height() - } -} -``` - -The one rule this convenience must not let you forget: inside a `#[cgp_impl]` block, `self` and `Self` mean the **context**, never the provider struct. The provider has no runtime value, so the macro rewrites every `self` to the context value and every `Self` to the context type — those are the only things that exist when the method runs. - -`#[cgp_impl]` desugars to `#[cgp_provider]` (or `#[cgp_new_provider]` when `new` is given). The block above is equivalent to writing the context out by hand and lowering it: - -```rust -#[cgp_new_provider] -impl<__Context__> AreaCalculator<__Context__> for RectangleArea -where - __Context__: HasDimensions, -{ - fn area(__context__: &__Context__) -> f64 { - __context__.width() * __context__.height() - } -} -``` - -The receiver `&self` became `__context__: &__Context__` — the receiver identifier is the snake-cased context name wrapped in double underscores — and `Self` in the `where` clause became the context type. When you write `for Context` explicitly, that name is used instead of `__Context__`, which is worth doing when you need to bound or refer to the context readably. - -## Impl-side dependencies - -A provider states what it needs from the context as bounds in its `where` clause, and those bounds — **impl-side dependencies** — are constraints the consumer trait never exposes. `CanCalculateArea` declares only `area`, while `RectangleArea` requires `Context: HasDimensions`; a caller bounding on `CanCalculateArea` never sees `HasDimensions`, so the requirement stays one level down and does not cascade through transitive callers. This is dependency injection through the `where` clause: any context satisfying the bound gains the capability automatically, and the wiring satisfies each bound by resolving it through the same context. See [impl-side dependencies](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/impl-side-dependencies.md) (online) for the full treatment, and [functions and getters](functions-and-getters.md) for the value-injection forms (`#[implicit]` arguments and getters) that read fields off the context through the same mechanism. - -## A consumer trait is still an ordinary trait - -A consumer trait can also be implemented directly on a context, exactly like a vanilla Rust trait, when code reuse across providers is not the goal. The consumer/provider split is a superset of ordinary traits, not a replacement: the provider machinery is what you opt into when a capability needs more than one implementation, and skipping it costs nothing for the simple case. You write `impl CanGreet for Person { ... }` as usual, and `person.greet()` resolves to that direct impl with no wiring involved. - -## Further reference - -For the full expansion and accepted syntax of each macro, see the online docs: [`#[cgp_component]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_component.md), [`#[cgp_impl]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_impl.md), [`#[cgp_provider]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_provider.md), [`#[cgp_new_provider]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_new_provider.md), and [`IsProviderFor`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/is_provider_for.md). For the concepts behind the split, see [consumer and provider traits](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/consumer-and-provider-traits.md), [bypassing coherence](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/coherence.md), and [impl-side dependencies](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/impl-side-dependencies.md). For sibling sub-skills, see [wiring](wiring.md), [checking](checking.md), [functions and getters](functions-and-getters.md), and [higher-order providers](higher-order-providers.md). diff --git a/docs/skills/cgp/references/error-extraction.md b/docs/skills/cgp/references/error-extraction.md deleted file mode 100644 index 4f11d7a8..00000000 --- a/docs/skills/cgp/references/error-extraction.md +++ /dev/null @@ -1,73 +0,0 @@ -# Extracting CGP compile errors - -How to turn a CGP compile error — often a wall of repeated failures naming generated types — into a compact, root-cause-first summary. This skill serves two roles, and which one applies depends on your task: an agent **extracting** an error reads raw compiler output and reduces it to a few facts, while an agent **delegating** hands a long error to a sub-agent so it never touches the main context. Both roles rely on the same shape knowledge, so read [The two shapes](#the-two-shapes-to-recognize) first, then jump to [Role 1 — extracting](#role-1--extracting-an-error-yourself) or [Role 2 — delegating](#role-2--delegating-the-extraction-to-a-sub-agent). - -**Prefer `cargo-cgp` first — this sub-skill is the fallback.** When [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) is available, it has already done this extraction: it rewrites a recognized CGP error into a `[CGP-Exxx]` headline over a root-cause dependency tree, which *is* the compact summary below. Reach for the manual technique here only when the tool is not installed, or when it leaves an error largely as `rustc` wrote it (a class it does not yet rewrite). The main [SKILL.md tooling section](../SKILL.md) covers installing and running the tool; everything below is for reading raw compiler output by hand. - -## Why extraction is a skill of its own - -CGP error output is disproportionately large and disproportionately misleading, so reading it well is a distinct task from fixing the code. Wiring is resolved [lazily](checking.md), so one broken link surfaces at every place that transitively needs it, and each failure quotes *generated* code — `IsProviderFor`, `DelegateComponent`, `CanUseComponent`, and the type-level `Symbol`/`Chars`/`PathCons` spines you never wrote. A single missing field can print screens of near-identical errors. Worse, some of that output is actively deceptive: a whole class of CGP errors reports that a trait's bounds are unsatisfied while *hiding* the dependency that actually failed. Reading such output inline, in the agent trying to fix the code, wastes context on noise and risks chasing a cause the output does not contain. - -## The two shapes to recognize - -Before decoding any nested type, read the *trait* in the error and decide which of two shapes you are looking at, because the shapes differ in whether the root cause is even present. The [macro-grammar](macro-grammar.md) decoder and the [checking](checking.md) playbook cover the full set; extraction needs the one binary distinction. - -A **surfaced** error carries the root cause. It is an `E0277` note chain, typically topped by `CanUseComponent` or `IsProviderFor`, that names a concrete missing bound such as `HasField`. This is what a [`check_components!`](checking.md) assertion produces, because the check requires `IsProviderFor` as a *direct* bound and forces the compiler to evaluate the provider's `where` clause. The concrete bound is the fact to extract — the compiler names it in a `help:`/"is not implemented" note, and the `required for …` notes trace the dependency path from it back to the check. - -A **hidden** error does not. It is an `E0599` "the method `greet` exists for struct `Person`, but its trait bounds were not satisfied", or an `E0277` that a consumer trait like `Person: CanGreet` is unsatisfied, and it names the consumer or provider trait and then stops — with no note descending to the missing field or dependency. This happens when broken wiring is exercised by *calling the consumer-trait method directly* rather than through a check: the compiler sees the consumer trait's blanket impl among the candidate impls, finds it inapplicable, and suppresses the nested bound that made it so. The cause is **absent**, not buried. Do not scan a hidden error for a root cause; there is none to find. The fix is to *promote* it into a surfaced error — add a `check_components!` for the failing component at the wiring site — and read that instead. - -## The cheap first move: grep for the suspected line - -Before committing to either role, try to answer your question with a grep, because the main agent can often confirm or dismiss a hypothesis without reading the log itself or spawning a sub-agent to read it. This is the move to reach for whenever you have a specific suspicion — a field you think you forgot, a key you think you wired twice, a `UseContext` you think is a cycle — and the failing build is captured to a file. Grep is not a third role so much as the way to avoid needing Role 2: a targeted search costs a few lines of output where delegating costs a whole sub-agent turn, so a hypothesis you can phrase as a pattern is one you should grep for rather than delegate. - -Grep the error headlines first, because that one search does the classifying. `grep -nE '^error' /tmp/cgp-error.txt` prints one line per error block — the code and the trait each names — which decides the [shape](#the-two-shapes-to-recognize) and, with the [macro-grammar decoder](macro-grammar.md), the class. A second grep for the class's signature then confirms the cause: `grep -n 'help:'` for a surfaced dependency leaf (a `HasField>` spells the field name letter-by-letter on one line), `conflicting implementation` for a duplicate key, `overflow evaluating` for a cycle, `does not contain any DelegateComponent entry` for an unwired component, `is not constrained` for an unconstrained generic, and so on. The [debugging guide's grep table](https://github.com/contextgeneric/cgp/blob/main/docs/guides/debugging.md#grep-for-the-suspected-line-instead-of-reading-the-whole-log) lists the signature line for every class. - -Two limits decide when grep gives way to a full read or a delegation. A **hidden** error cannot be grepped: if the only headline is an `E0599` "method … exists … but its trait bounds were not satisfied", the cause is absent from the output, so promote it with a `check_components!` and re-run rather than searching for a leaf that is not there. And when the headline grep shows several unrelated classes at once, or the cause hides behind an elided `...` whose full form sits in a `long-type-….txt` file, the search will not converge — that is the point to escalate to [Role 2](#role-2--delegating-the-extraction-to-a-sub-agent) and let a sub-agent absorb the whole log. The rule is the same throughout: a targeted question is a grep, an open-ended read is a delegation. - -## Role 1 — extracting an error yourself - -You are in this role whenever you are the one reading the raw output — as a sub-agent handed the job, or as the main agent facing an error short enough to read inline. Your product is the [compact summary](#reduce-to-the-compact-summary) below, never the raw dump. - -### Capture the output without flooding context - -Capture the compiler output to a file rather than letting it stream into your transcript, and target the smallest unit that reproduces the failure. A whole-workspace build multiplies the cascade across crates; a single crate, test, example, or ten-line scratch module shrinks it to the one thing you care about. The commands are just your project's ordinary build and test invocations — nothing CGP-specific: - -```bash -# Target the smallest failing unit and redirect everything to a scratch file. -cargo check -p 2>&1 | tee /tmp/cgp-error.txt - -# or a single test / example that exercises the failing wiring: -cargo test -p --test 2>&1 | tee /tmp/cgp-error.txt -``` - -Two details recur, both plain `rustc` behavior rather than anything CGP-specific. When a type in the error is elided as `...`, the compiler writes its full form to a file named in a final note (`the full name for the type has been written to '….long-type-….txt'`); that elided middle is frequently the one segment that reveals *which* context or path the error is about, so read that file when the cause hinges on a long type. And watch for the **near-contradiction** shape — "the trait `X` is not implemented for `T`" immediately followed by a `help:` note that `X` *is* implemented for `T` — which means an impl exists but a nested bound it carries does not hold, or two candidates are ambiguous; trust the error, not the `help:`. - -When the cause hinges on *what the macro emitted* rather than on the message itself, stop reading the error and read the expansion. Reach for `cargo cgp expand` first: it prints the crate after macro expansion with CGP's type-level constructs resugared, so a field tag that the error rendered as a `Chars` spine reads `Symbol!("height")` in the expansion — the generated code is often easier to read than the diagnostic about it. Narrow it to the part you care about, since a whole crate's expansion is long and a filtered one keeps your context clear: - -```sh -cargo cgp expand --lib --item AreaCalculator # a trait: every impl of it — what a component generated -cargo cgp expand --lib --item contexts::MockApp # a type: its HasField impls and its wiring entries -cargo cgp expand --lib > /tmp/expanded.rs # or redirect the whole thing and grep it -``` - -Pass `--lib` or `--bin NAME` when the package has several targets, or cargo refuses to run. Plain `cargo expand` is the fallback where `cargo cgp expand` is unavailable — it shows the same expansion without the resugaring — and in a project set up with the CGP test utilities the `snapshot_*!` helpers from `cgp-macro-test-util` additionally pin an expansion as a reviewable snapshot. The [macro-grammar](macro-grammar.md) skill covers how to read the expanded impls once you have them. - -### Reduce to the compact summary - -Reduce the output to the same few facts — the anatomy a CGP error catalog records — and nothing more: - -- **Class and code** — the error code(s) and the trait(s) named (`E0599` on a provider trait, `E0277` through `CanUseComponent`, `E0119` conflicting `DelegateComponent`, `E0207` unconstrained generic, and so on). -- **Hidden or surfaced** — whether the root cause is present in the output at all. -- **Root cause and position** — if surfaced, the concrete failing bound and where it sits (in the compiler's `help:` note, near the last or second-to-last block of a cascade, or inside the `long-type-….txt` file); if hidden, say so plainly. -- **Recommended next action** — fix the named field or wiring, promote a hidden error with a `check_components!`, break a cycle, remove a duplicate key — whatever the class implies. - -A good summary is a few lines, not a transcript. Never hand back the raw output; that defeats the purpose of extracting it. - -## Role 2 — delegating the extraction to a sub-agent - -You are in this role when you are the main agent and the error is long — a deep cascade, many crates, or output that runs to screens. Spawn a sub-agent to read it and return only the [compact summary](#reduce-to-the-compact-summary); the sub-agent absorbs the wall of text while you keep your own context clean. Give the sub-agent three things: the exact command to run (or the path to an already-captured output file), this skill so it knows the two shapes and the summary format, and the instruction to return *only* the summary. This is the intended workflow both when documenting an error class — where a sub-agent gathers the facts from a failing reproduction — and in an ordinary debugging session, where a sub-agent reads an error too long to justify reading inline. When the returned summary says the error is hidden, the follow-up is almost always to add a check and re-run, which itself may be worth delegating. - -## Further reference - -- Sibling skills: [checking](checking.md) for why wiring is lazy and how checks force a surfaced error; [macro-grammar](macro-grammar.md) for the full error decoder and how to read an expansion; [wiring](wiring.md) for the delegation mechanics behind a conflict or cycle. -- Online: the [error catalog](https://github.com/contextgeneric/cgp/tree/main/docs/errors) documents each error class and where its root cause sits, and the [debugging guide](https://github.com/contextgeneric/cgp/blob/main/docs/guides/debugging.md) is the full tracing playbook. diff --git a/docs/skills/cgp/references/error-handling.md b/docs/skills/cgp/references/error-handling.md deleted file mode 100644 index 993b84d7..00000000 --- a/docs/skills/cgp/references/error-handling.md +++ /dev/null @@ -1,135 +0,0 @@ -# Error handling - -CGP's modular error handling: one shared abstract `Error` type per context, plus pluggable behavior for constructing that error from a source error and for attaching detail to it. - -Generic CGP code must be able to fail without naming a concrete error type. A provider that hits a fallible operation gets back a `parse error`, an `io::Error`, or a string, yet it is generic over the context and cannot commit to `anyhow::Error` or any other choice — that choice belongs to the application assembling the context. CGP resolves this with three cooperating [components](components.md): `HasErrorType` gives the context one abstract `Self::Error`, `CanRaiseError` constructs that abstract error from a concrete source error, and `CanWrapError` enriches an existing abstract error with extra detail. The concrete error type and the raising/wrapping behavior are decided once, at [wiring](wiring.md) time, by whichever error backend the context plugs in. - -A note on imports: the consumer traits `HasErrorType`, `CanRaiseError`, and `CanWrapError` come through `use cgp::prelude::*;`, but the wiring keys and backend providers below do not. The component markers (`ErrorTypeProviderComponent`, `ErrorRaiserComponent`, `ErrorWrapperComponent`) live under `cgp::core::error`, and the backend providers (`RaiseFrom`, `ReturnError`, `DebugError`, `DisplayError`, and the rest) live under `cgp::extra::error`, so a module that wires error handling imports the specific names it uses, for example `use cgp::core::error::ErrorRaiserComponent;` and `use cgp::extra::error::RaiseFrom;`. - -## `HasErrorType`: the shared abstract error - -`HasErrorType` is the abstract-type component that gives a context a single shared `Error` type, and every fallible CGP operation refers to it. It is declared with `#[cgp_type]`, so it behaves like any other [abstract type](abstract-types.md) — a trait with one associated type, wired through a provider rather than hand-implemented: - -```rust -#[cgp_type] -pub trait HasErrorType { - type Error: Debug; -} - -pub type ErrorOf = ::Error; -``` - -The `Debug` bound lets `Self::Error` flow into `.unwrap()` and straightforward logging without an extra constraint, and it is enforced on whatever concrete type a context chooses. `ErrorOf` is the convenient spelling of the associated-type path. Generic code that may fail returns `Result` (or `Result>`) and never names a concrete error. - -Centralizing the error type on one trait is what lets errors compose. A context trait that may fail depends on `HasErrorType`, so every such trait refers to the *same* error; if each declared its own associated `Error`, a context bounded by several of them would face several incompatible error types with no way to unify them. `HasErrorType` carries no methods — it only declares the type. The behavior of producing errors lives in the traits that build on it. The preferred way to author such a trait is [`#[use_type(HasErrorType.Error)]`](abstract-types.md), which adds `HasErrorType` as a supertrait *and* rewrites a bare `Error` in the signatures to `::Error` — so you write neither `: HasErrorType` nor `Self::Error` by hand. - -Because `#[cgp_type]` generates a `UseType` blanket impl, a context fixes its error type by wiring the error-type component to `UseType`, exactly as for any abstract type: - -```rust -#[cgp_component(Validator)] -#[use_type(HasErrorType.Error)] -pub trait CanValidate { - fn validate(&self) -> Result<(), Error>; -} - -pub struct App; - -delegate_components! { - App { - ErrorTypeProviderComponent: UseType, - } -} -``` - -Here `#[use_type(HasErrorType.Error)]` makes `CanValidate` depend on the shared abstract error and lets `validate` name it as the bare `Error`, and `App` fixes that error to `String`. The standalone backends (`cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`) supply ready-made providers that set `Error` to their respective library types instead. A context can equally implement the trait directly — `impl HasErrorType for App { type Error = String; }` — which makes plain that it is an ordinary trait with a `Debug`-bounded associated type. - -## `CanRaiseError` and `CanWrapError`: producing and enriching the error - -`CanRaiseError` is the consumer trait for turning a concrete source error into the context's abstract error, and `CanWrapError` is the companion that attaches detail to an existing one. Both import the error type with `#[use_type(HasErrorType.Error)]` — so they name it as the bare `Error` and gain `HasErrorType` as a supertrait — and both are `#[cgp_component]`s that delegate per type so a context can handle each source error or detail with a different provider: - -```rust -#[cgp_component(ErrorRaiser)] -#[derive_delegate(UseDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanRaiseError { - fn raise_error(error: SourceError) -> Error; -} - -#[cgp_component(ErrorWrapper)] -#[derive_delegate(UseDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanWrapError { - fn wrap_error(error: Error, detail: Detail) -> Error; -} -``` - -`raise_error` takes the source error by value and returns the abstract error; `wrap_error` takes the current abstract error plus a `Detail` and returns an enriched one. Both are associated functions with no `self` receiver — raising and wrapping are properties of the context *type*, so generic code calls `Context::raise_error(source)` and `Context::wrap_error(err, detail)` where only the type parameter is in scope. The `#[cgp_component(...)]` attribute names the provider traits `ErrorRaiser` and `ErrorWrapper`, and `#[derive_delegate(UseDelegate<...>)]` makes each dispatch per `SourceError` or `Detail` type through a delegation table. - -A provider written against these bounds names neither the context nor its concrete error type: - -```rust -#[cgp_component(Loader)] -#[use_type(HasErrorType.Error)] -pub trait CanLoad { - fn load(&self, path: &str) -> Result; -} - -#[cgp_impl(new LoadOrFail)] -#[uses(CanRaiseError, CanWrapError)] -#[use_type(HasErrorType.Error)] -impl Loader { - fn load(&self, path: &str) -> Result { - if path.is_empty() { - let err = Self::raise_error("empty path".to_owned()); - return Err(Self::wrap_error(err, format!("while loading {path}"))); - } - Ok(format!("contents of {path}")) - } -} -``` - -The provider requires `CanRaiseError` to turn a message into the abstract error and `CanWrapError` to attach context as it propagates — both [impl-side dependencies](components.md) that any wired context satisfies by plugging in providers. The context decides, through wiring, what concrete error type `load` actually produces. - -## Wiring the behavior: error-backend providers - -A context gains raising and wrapping by wiring `ErrorRaiserComponent` and `ErrorWrapperComponent` to providers, exactly like any other component. The `cgp-error-extra` crate supplies a family of zero-sized providers (the [providers](components.md) are markers with no runtime value) that are generic over the context's error type and capture cross-cutting strategies independent of any one error library; they sit alongside the standalone backends, which specialize to a concrete library error such as `anyhow::Error`. A typical context wires a mix: a backend for the concrete error type plus these generic providers for the strategies. The raisers, wrappers, and their bounds are: - -- `RaiseFrom` implements `ErrorRaiser` by converting through `From` — it raises any source error whose `Context::Error: From`, the default choice when the abstract error already absorbs the source. -- `ReturnError` implements `ErrorRaiser` for the case where the source *is* the abstract error (`HasErrorType`), returning it untouched. -- `RaiseInfallible` implements `ErrorRaiser` for `core::convert::Infallible`, producing the error by an empty `match` that can never run — letting code parameterized over a fallible operation be wired uniformly even when the chosen operation cannot fail. -- `PanicOnError` implements `ErrorRaiser` by `panic!`-ing with the source error's `Debug` output instead of returning a value — for contexts that treat an error as a fail-fast fault, such as tests. -- `DiscardDetail` implements `ErrorWrapper` by dropping the detail and returning the error unchanged — the wrapping no-op, for error types that cannot carry extra context. -- `DebugError` and `DisplayError` implement *both* components by formatting the source error or detail into a `String` and forwarding to the context's own `CanRaiseError` / `CanWrapError` — `DebugError` via `Debug`, `DisplayError` via `Display`/`to_string()`. Both live behind the crate's `alloc` feature. - -The simplest wiring delegates to a single provider. Wiring `RaiseFrom` lets `App` raise any source error its abstract error implements `From` for: - -```rust -delegate_components! { - App { - ErrorRaiserComponent: RaiseFrom, - } -} -``` - -The string-formatting providers are designed to *compose* with the others rather than replace them: `DebugError` and `DisplayError` do not know the context's error type, they only reduce a `Debug` or `Display` value to a `String` and hand it off, so the context must separately wire a provider that handles the `String` source. Because both components dispatch per source-error type, the idiomatic wiring lists one entry per source error — a concrete string-raising rule plus formatting redirects for everything else — opened on the component with the `open` statement of `delegate_components!`: - -```rust -delegate_components! { - App { - open ErrorRaiserComponent; - - @ErrorRaiserComponent.String: RaiseFrom, - @ErrorRaiserComponent.ParseError: DebugError, - } -} -``` - -The `open ErrorRaiserComponent;` header opens the component for per-type wiring, and each `@ErrorRaiserComponent.: Provider` entry assigns the provider for one source-error type, folded directly into `App`'s own table — `open` needs no `#[derive_delegate]` of its own because every `#[cgp_component]` already generates the `RedirectLookup` impl it dispatches through. The legacy equivalent writes the same per-type entries into a separate `UseDelegate` nested table; that form is still common in existing code but is slated for deprecation, so prefer `open` for new wiring. See [wiring](wiring.md) for both forms. - -A raised `String` is converted straight into the abstract error by `RaiseFrom`, while a raised `ParseError` is formatted with `Debug` by `DebugError` and then routed back through the `String` entry — which `RaiseFrom` handles — yielding one coherent error type from two unrelated sources. The choice of provider is therefore also a statement about which source errors the context accepts and how, and each wiring is verified with `check_components!` like any other. - -## Related constructs - -`HasErrorType` is an [abstract type](abstract-types.md) declared with `#[cgp_type]`, so it is wired with `UseType` or a backend provider the same way every abstract type is. `CanRaiseError` and `CanWrapError` are ordinary [components](components.md) that supertrait it, wired with `delegate_components!` as covered in [wiring](wiring.md), and their `#[derive_delegate(UseDelegate<...>)]` is what lets a context dispatch raising and wrapping per source-error or detail type through a delegation table. - -Further reference (online): [components/has_error_type.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/has_error_type.md), [components/can_raise_error.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/can_raise_error.md), [providers/error_providers.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/error_providers.md). diff --git a/docs/skills/cgp/references/extensible-data.md b/docs/skills/cgp/references/extensible-data.md deleted file mode 100644 index aed58ca3..00000000 --- a/docs/skills/cgp/references/extensible-data.md +++ /dev/null @@ -1,147 +0,0 @@ -# Extensible data - -Treat a struct as a product of named fields and an enum as a sum of named variants, so that generic code can read, build, deconstruct, and convert any data type by its type-level field and variant names rather than by naming the concrete type. - -A plain Rust `struct` or `enum` is opaque to generic code: nothing about "the `first_name` field" or "the `Circle` variant" can be named through a type parameter, so a struct literal must list every field at one site and a `match` must spell out every variant. Extensible data breaks that by deriving a type-level description of a type's shape — its fields or variants, each tagged by name — and the machinery to build, take apart, and convert values through that description. This brings row polymorphism and structural sum types to Rust, resolved entirely at compile time, and it is what lets independent components each contribute one field or handle one variant without knowing the whole type. The result feeds directly into CGP wiring: builders, variant dispatchers, and structural casts all consume the field-level machinery these derives generate. A context is just such a data type when it flows through providers this way. - -The whole family is dual. A record is a product (every field present at once); an enum is a sum (exactly one variant present). The two share their presence markers, their casting traits, and their dispatch machinery, so reading one half tells you the shape of the other. - -## The umbrella derive and its shape-specific faces - -`#[derive(CgpData)]` is the high-level entry point: applied to a struct it emits the full record machinery, applied to an enum the full variant machinery, dispatching on the item kind. `#[derive(CgpRecord)]` and `#[derive(CgpVariant)]` are the same code paths restricted to one shape — use them when the type is always a struct or always an enum and you want the name to say so; applying the wrong one is a type error. The narrower building-block derives (`HasFields`, `HasField`, `BuildField`, `ExtractField`, `FromVariant`) each emit one slice of that output when you want only part of it. - -```rust -#[derive(CgpData)] -pub struct Person { pub first_name: String, pub last_name: String } - -#[derive(CgpData)] -pub enum Shape { Circle(Circle), Rectangle(Rectangle) } -``` - -Field tags drive everything generated. A named struct field or an enum variant is keyed by the type-level string `Symbol!("name")`, and an unnamed field of a tuple struct by its positional `Index`; that tag is what addresses the field across every generated impl. One restriction shapes the variant side: a derivable enum must follow the sum-of-products form, where each variant holds exactly one unnamed payload — wrap a richer payload in a dedicated struct so the variant's value stays a single nameable type. A fieldless, multi-field, or struct-style variant is a compile error. - -## Records: the whole-struct field view - -`#[derive(HasFields)]` gives a type its whole-shape view, the foundation everything else builds on. For a struct it implements `HasFields` with a `Fields` associated type that is a [`Product!`](type-level-primitives.md) of `Field` entries — the type-level spelling of the struct's layout, one entry per field tagged by name: - -```rust -impl HasFields for Person { - type Fields = Product![ - Field, - Field, - ]; -} -``` - -The derive also emits the conversions that move values in and out of that representation: `ToFields` turns an owned value into its `Fields` product, `FromFields` rebuilds the value from one, and `ToFieldsRef` borrows it as a product of references. Generic algorithms bound `Context: HasFields` (or `ToFields`/`FromFields`) and fold over `Context::Fields` structurally, never naming the concrete struct. This whole-shape view is distinct from per-field indexed access: reading a single field by name is `HasField`, the dependency-injection capability that getter traits resolve against (see [functions and getters](functions-and-getters.md)). A struct that wants both derives both. - -## Records: building a struct field by field - -The capability `#[derive(CgpRecord)]` and `#[derive(BuildField)]` add on top of reading is incremental, type-checked construction, exposed through the builder trait family. Construction runs through a *partial record* — a generated companion struct `__Partial{Name}` carrying one `MapType` marker per field that records whether that field is present yet. The marker `IsPresent` stores the field's real value, `IsNothing` stores `()`, so a partial record with every marker `IsNothing` is an empty builder and one with every marker `IsPresent` is a fully populated value. The family walks between these states. - -```rust -let employee: Employee = Employee::builder() // every field IsNothing - .build_from(person) // first_name + last_name now IsPresent - .build_field(PhantomData::, 1) // employee_id now IsPresent - .finalize_build(); // exists only at the all-present configuration -``` - -`HasBuilder::builder()` produces the empty partial record; `BuildField::build_field` flips one marker from `IsNothing` to `IsPresent`; and `FinalizeBuild::finalize_build` turns the partial record back into the concrete struct. Underneath, both `BuildField` and its reverse `TakeField` (the `IsPresent → IsNothing` direction) are blanket impls over a single per-field primitive `UpdateField`, which changes one field's marker and returns the old value alongside the rebuilt partial. The tracking is what makes the pattern safe: `finalize_build` is implemented *only* for the all-`IsPresent` configuration, so finalizing with any field still absent is a compile error rather than a runtime panic, and the order in which fields are built does not matter. The reverse direction is equally useful — `IntoBuilder` turns a complete struct into an all-present partial, and `TakeField` removes fields one at a time. - -That all-present strictness is sometimes too rigid — a record may have fields with sensible defaults or ones genuinely allowed to be absent. The `cgp-field-extra` **optional-field** extension relaxes it in two controlled ways while reusing the same `UpdateField` machinery: it can fill an unset field with `Default::default()` at finalize time, and it can track a field as an `Option` (the `IsOptional` marker) so absence becomes a runtime value rather than a compile error. Reach for it when a struct has optional or defaulted fields; the strict all-present builder above remains the default. - -## Records: the extensible builder pattern - -`CanBuildFrom` (one of the structural casts below) is what lets a builder absorb the shared fields of an entire source struct in one `build_from` step: it recurses over the source's field product, using `TakeField` to pull each field out and `BuildField` to write it into the target builder. So a small struct like a database client can be merged into a larger application struct without either type naming the other — they share only field names, matched at the type level. This is the basis of the *extensible builder pattern*, where the construction of one context is split across several independent providers, one per subsystem, none of which knows the final type or each other. Each provider builds a small output struct, and a dispatcher merges every output into the target's builder before finalizing. - -```rust -delegate_components! { - FullAppBuilder { - HandlerComponent: - BuildAndMergeOutputs, - } -} -``` - -Because the dispatcher is generic over the target struct and the provider list, swapping a subsystem means changing one entry, and selecting among several target structs is a matter of code-based dispatch. The `BuildAndMergeOutputs` combinator and the rest of this routing live in [handlers](handlers.md). - -## Variants: constructing and deconstructing an enum - -For an enum, `#[derive(HasFields)]` produces a `Fields` that is a [`Sum!`](type-level-primitives.md) of `Field` entries built on the `Either`/`Void` spine, mirroring the product side. Construction is `FromVariant`, generated per variant by `#[derive(FromVariant)]`: it builds the enum from one variant chosen by a type-level tag, so generic code parameterized over a `Tag` can construct whichever variant it was asked for. - -```rust -fn wrap_circle(circle: Circle) -> Shape { - Shape::from_variant(PhantomData::, circle) // == Shape::Circle(circle) -} -``` - -Deconstruction is the dual of the builder and runs through a *partial variant* — the companion enum `__Partial{Name}` generated by `#[derive(ExtractField)]`, carrying one `MapType` marker per variant just as a partial record carries one per field. The crucial difference is the absence marker: a record uses `IsNothing` (storing `()`), while a variant uses `IsVoid`, mapping a ruled-out variant to the uninhabited `Void` type. `HasExtractor::to_extractor` converts the enum into a partial variant with every variant still `IsPresent`, and `ExtractField::extract_field` tries to pull out one variant, returning `Ok(value)` if the value is that variant or `Err(remainder)` — the partial variant with that variant flipped to `IsVoid` — otherwise. - -```rust -fn area(shape: Shape) -> f64 { - match shape.to_extractor().extract_field(PhantomData::) { - Ok(circle) => core::f64::consts::PI * circle.radius * circle.radius, - Err(remainder) => { - // remainder's type now has Circle ruled out (IsVoid); try the next variant - let rect = remainder - .extract_field(PhantomData::) - .finalize_extract_result(); // remainder is now empty; this cannot fail - rect.width * rect.height - } - } -} -``` - -Marking extracted variants as `IsVoid` is what gives compile-time exhaustiveness without a wildcard arm. Each failed `extract_field` rules out one more variant in the type, so after every variant has been tried the remainder has every marker `IsVoid` and is therefore uninhabited. `FinalizeExtract::finalize_extract` discharges such a remainder with an empty `match`, sound precisely because no value can reach it — and `FinalizeExtractResult::finalize_extract_result` is the convenience wrapper that collapses the final `Result` into its `Ok` value. Add a variant to the enum without handling it and the final remainder becomes inhabited again, so the code fails to compile until the new variant is covered, recovering the guarantee a concrete `match` gives. `HasExtractorRef` and `HasExtractorMut` provide the same machinery over borrows. - -## Variants: the extensible visitor pattern - -Routing a value to the handler for its current variant is the *extensible visitor pattern*, which solves the expression problem: new variants can be added without touching the handlers for the others, and the same handler set can serve several enums that share variants. The logic for each variant lives in its own provider, and a dispatcher derives one extract-and-handle step per variant from the enum's `Fields`, running them as a pipeline that short-circuits on the first matching variant and threads the remainder forward otherwise. - -```rust -delegate_components! { - Interpreter { - ComputerComponent: - UseInputDelegate: EvalAdd, // one provider per variant - Times: EvalMultiply, - Literal: EvalLiteral, - }>, - } -} -``` - -The `MathExpr` entry routes the whole enum through a thin context-specific provider that defers to the matcher combinator; that wrapper exists to break the trait-resolution cycle between the matcher and the per-variant providers it dispatches to. The matcher combinators (`MatchWithValueHandlers` and its by-reference form) and the per-variant handler families live in [handlers](handlers.md). - -## The type-level spines underneath - -Both halves rest on the same right-nested type-level lists, kept brief here and covered fully in [type-level primitives](type-level-primitives.md). A struct's `Fields` is a `Product![A, B, C]`, which desugars to `Cons>>` over the `Cons`/`Nil` record spine — a list terminated by the constructible empty `Nil`, because an empty record is a valid value. An enum's `Fields` is a `Sum![A, B]`, which desugars to `Either>` over the `Either`/`Void` variant spine — a chain that branches at each step and terminates in the uninhabited `Void`, because an empty choice has no value to pick. The lowercase `product![..]` builds a value of the matching `Product!` type. Generic providers walk these spines one element at a time, which is exactly what no plain tuple or enum permits in generic code. - -The machinery above computes new spines from old ones through a small type-level list algebra you will meet in the traits it expands to: `AppendProduct` adds one field to the end of a product, `ConcatProduct` splices two products together, and `MapFields` rewrites every entry uniformly (the operation behind producing a partial record from a full one). All three are pure functions from type lists to type lists, evaluated by the compiler during type checking at no runtime cost — building a record appends, merging records concatenates, and forming a partial record maps a marker over every field. - -## Structural casts between records and variants - -Two types that share a subset of named fields or variants convert into one another generically, with no hand-written `From`/`TryFrom`, through the casting traits — every conversion is just routing each named entry to the matching slot in the target. `CanUpcast` lifts a value of a narrow enum into a wider one whose variants are a superset; it always succeeds, since every source variant has a home in the target, and it walks the source's variants, extracting each and reconstructing it via `FromVariant`. `CanDowncast` goes the other way, narrowing a wide enum into a smaller one and succeeding only if the value's current variant exists in the target, otherwise handing back a remainder; `CanDowncastFields` is the same operation on a remainder, so downcasting against several candidates chains a `downcast` followed by `downcast_fields`. `CanBuildFrom` is the record counterpart already met above, assembling a target builder out of the fields of one or more sources. - -```rust -let wide = FooBar::Foo(1).upcast(PhantomData::); // always succeeds -assert_eq!(wide, FooBarBaz::Foo(1)); - -FooBarBaz::Bar("hi".into()).downcast(PhantomData::).ok(); // Some(FooBar::Bar(..)) -FooBarBaz::Baz(true).downcast(PhantomData::).ok(); // None — Baz has no home in FooBar -``` - -Upcasting is also how a provider constructs a value using only the subset of variants it cares about: it builds a small local enum and upcasts it into the full type — the variant-side analog of reading a field through a getter. - -## Dispatching over extensible data - -The payoff of exposing data shape at the type level is that *dispatch* becomes generic: a record builder routes each field to the provider that produces it, and a variant visitor routes each value to the provider for its current variant. Both are realized with the dispatch combinators — `BuildAndMergeOutputs` on the record side, `MatchWithValueHandlers` on the variant side — which derive one per-field or per-variant step from the type's `Fields` and sequence them. Those combinators and the handler families they build on are documented in [handlers](handlers.md); the wiring tables that name a provider per [component](components.md) are the ordinary `delegate_components!` entries shown above. - -## Further reference - -Online docs (current as of CGP v0.8.0): [concepts/extensible-records.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/extensible-records.md), [concepts/extensible-variants.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/extensible-variants.md), the derive references under [reference/derives/](https://github.com/contextgeneric/cgp/tree/main/docs/reference/derives) (`derive_cgp_data.md`, `derive_cgp_record.md`, `derive_cgp_variant.md`, `derive_has_fields.md`, `derive_build_field.md`, `derive_extract_field.md`, `derive_from_variant.md`), the trait references [traits/has_builder.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/has_builder.md), [traits/extract_field.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/extract_field.md), [traits/from_variant.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/from_variant.md), [traits/has_fields.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/has_fields.md), and [traits/cast.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/cast.md), and the type macros [macros/product.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/product.md) and [macros/sum.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/sum.md). diff --git a/docs/skills/cgp/references/functions-and-getters.md b/docs/skills/cgp/references/functions-and-getters.md deleted file mode 100644 index 2c33b032..00000000 --- a/docs/skills/cgp/references/functions-and-getters.md +++ /dev/null @@ -1,301 +0,0 @@ -# Functions and Getters - -The ergonomic surface of basic CGP — `HasField` field access, `#[cgp_fn]` single-implementation capabilities, `#[implicit]` arguments, and getter traits — the constructs that let CGP code read like ordinary Rust functions and accessors. - -## How field access works underneath: `HasField` - -Every value a provider reads out of its context flows through one tiny consumer trait, `HasField`, which keys a single field by a *type-level* name rather than by the concrete struct it lives in. A provider is generic over its context and cannot reach into a struct it does not know, so instead of naming the field directly it demands one by tag: a `where`-clause bound `Context: HasField` says "any context wired to me must carry a `String` field called `name`," and the trait system supplies it. This makes field access an [impl-side dependency](components.md) — a requirement hidden from the trait interface and satisfied automatically by any matching context. Assume `use cgp::prelude::*;` throughout; the CGP version is v0.8.0. - -The trait carries the field's type as an associated `Value` and returns a reference, taking a `PhantomData` argument whose only job is to tell the compiler which field is meant when several `HasField` impls are in scope: - -```rust -pub trait HasField { - type Value; - fn get_field(&self, _tag: PhantomData) -> &Self::Value; -} -``` - -The `Tag` is a type-level name, and CGP has two kinds. A named struct field is keyed by `Symbol!("field_name")`, the type-level string of its identifier; a tuple field is keyed by `Index`, the type-level natural number of its position. Both are [type-level primitives](type-level-primitives.md) — types with no values — which is exactly why `get_field` needs the `PhantomData` argument to carry one at the call site. A `HasFieldMut: HasField` companion adds `get_field_mut` returning `&mut Self::Value` for the rarer mutable case. - -Reading a field is the PhantomData tag-inference trick in practice. Inside a provider body you write `self.get_field(PhantomData)` and let Rust infer the tag from the surrounding bound, or pin it explicitly with `self.get_field(PhantomData::)` when more than one field could match: - -```rust -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasField, -{ - fn greet(&self) { - println!("Hello, {}!", self.get_field(PhantomData)); - } -} -``` - -You almost never write `HasField` impls by hand. The companion derive does it for you. - -## Generating field access: `#[derive(HasField)]` - -`#[derive(HasField)]` turns a struct's concrete fields into the type-level entries the trait system looks up, emitting one `HasField` and one `HasFieldMut` impl per field and leaving the struct definition untouched. It is the bridge between an ordinary Rust struct and constraint-based field access: without it, a struct's fields are invisible to CGP, and every getter would be hand-written. For a named struct, - -```rust -#[derive(HasField)] -pub struct Person { - pub name: String, - pub age: u8, -} -``` - -the derive emits a `Symbol!`-keyed pair per field — `impl HasField for Person { type Value = String; … }` reading `&self.name`, and the same for `age` — so `Person` satisfies `HasField` exactly as the `GreetHello` bound above requires. A tuple struct expands identically except each field is keyed by its positional `Index` instead of a `Symbol!`: - -```rust -#[derive(HasField)] -pub struct Rectangle(pub f64, pub f64); -// → impl HasField> for Rectangle { type Value = f64; … &self.0 } -// → impl HasField> for Rectangle { type Value = f64; … &self.1 } -``` - -Generic parameters thread through faithfully, and field access also flows through smart pointers — `HasField` has a blanket impl over any `Deref` target that has the field, so a `Box` resolves `get_field` to the inner struct. Deriving `HasField` is the one thing a context must do for every higher-level construct on this page — `#[cgp_fn]`, `#[implicit]`, and both getter macros all desugar into `HasField` bounds. - -## Single-implementation capabilities: `#[cgp_fn]` - -`#[cgp_fn]` turns a plain function into a CGP capability that every context gains automatically, with no wiring step at all. A full [component](components.md) defines a consumer trait, a provider trait, and a delegation table so that many providers can be swapped per context; `#[cgp_fn]` is the lightweight counterpart for the common case where a capability has a single natural definition. You write the body as if `self` were concrete, and the macro emits a trait plus a *blanket* impl over a generic context — so the method becomes available on every type that satisfies the body's impl-side dependencies, with no `delegate_components!` block and no provider struct anywhere. - -The function name in snake case becomes the method name, and the trait name defaults to that name in PascalCase. Given: - -```rust -#[cgp_fn] -pub fn rectangle_area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height -} -``` - -the macro emits a `RectangleArea` trait whose method takes no arguments, and a blanket impl over the reserved context type `__Context__` in which each `#[implicit]` parameter became a `HasField` bound and a `get_field` binding at the top of the body: - -```rust -pub trait RectangleArea { - fn rectangle_area(&self) -> f64; -} - -impl<__Context__> RectangleArea for __Context__ -where - Self: HasField - + HasField, -{ - fn rectangle_area(&self) -> f64 { - let width: f64 = self.get_field(PhantomData::).clone(); - let height: f64 = self.get_field(PhantomData::).clone(); - width * height - } -} -``` - -That generated blanket impl is the whole point of the macro, so it is worth seeing: the context type parameter is literally `__Context__` and references to it inside the impl read as `Self`. Pass an identifier to override the default trait name, which is useful when a verb-style name reads better — `#[cgp_fn(CanCalculateRectangleArea)]` generates `CanCalculateRectangleArea` instead of `RectangleArea`. - -Generics and `where` clauses are handled with a deliberate split: every generic parameter in the function's `<...>` list goes onto *both* the trait and the impl, while the function's `where` bounds land only on the impl, hidden from the trait as impl-side dependencies. A generic area function makes this concrete: - -```rust -#[cgp_fn] -pub fn rectangle_area( - &self, - #[implicit] width: Scalar, - #[implicit] height: Scalar, -) -> Scalar -where - Scalar: Mul + Copy, -{ - width * height -} -``` - -Here `Scalar` appears on `RectangleArea` and its impl, while `Scalar: Mul + Copy` stays on the impl only, ordered before the implicit `HasField` bounds (which are always appended last). One restriction is intentional: `#[cgp_fn]` does not support generics on the *method* itself — method-level generics belong to the trait and impl, and the rare genuine need for them is an advanced case better written as an explicit blanket impl or a full component. - -## Field access dressed as a function argument: `#[implicit]` - -The `#[implicit]` attribute marks a function argument as sourced from a context field instead of from the caller, so a provider reads like a function taking parameters while behaving like one injecting dependencies. This is the recommended on-ramp to CGP: a programmer who understands functions and arguments can write a complete provider without first meeting `HasField`, `Symbol!`, or `PhantomData`. Strongly prefer implicit arguments in basic code — they keep CGP looking like ordinary Rust. The argument name doubles as the field name, so `#[implicit] width: f64` reads as "this function needs a `width` of type `f64`," and the macro removes the argument from the signature, adds the matching `HasField` bound, and binds the value at the top of the body (as the `#[cgp_fn]` expansion above shows). - -The argument type controls how the field is read, following a small set of rules so the body always receives exactly the declared type. An owned type such as `f64` or `String` reads the field by reference and appends `.clone()`, leaving the context's field intact; the one special case to memorize is `&str`, which is backed by a `String` field and read with `.as_str()` rather than `.clone()`, letting the body borrow without forcing the context to store a `&str`: - -```rust -#[cgp_fn] -pub fn greet(&self, #[implicit] name: &str) { - println!("Hello, {}!", name); -} -// bound: HasField -// binding: let name: &str = self.get_field(PhantomData::).as_str(); -``` - -Three rules constrain where `#[implicit]` may appear. The function must take `self` as its first argument, since the field is read from it; the argument pattern must be a bare identifier, not a destructuring or `mut` pattern (clone inside the body for a mutable local); and a mutable implicit argument — one whose type carries a `&mut` (a `&mut T`, `&mut [T]`, or `Option<&mut T>`) — must be the *only* implicit argument (and needs a `&mut self` receiver), since it reads through `get_field_mut` and borrows the whole context exclusively — immutable implicits are shared borrows and combine freely in any number. The access mode follows the argument's own type, not the receiver's: only an argument carrying a `&mut` reads through `get_field_mut` (a `&mut [T]` via an `AsMut<[T]>` field, an `Option<&mut T>` via `.as_mut()`). `#[implicit]` is usable in both `#[cgp_fn]` and the methods of a `#[cgp_impl]` provider, with the same desugaring in each — inside `#[cgp_impl]` the `HasField` bounds simply join the provider impl's `where` clause. - -## Importing capabilities: `#[uses]` - -`#[uses(...)]` adds `Self: Trait<...>` bounds to a provider's `where` clause, written to read like a `use` import of the capabilities the body depends on. A provider that calls another `#[cgp_fn]` capability, or a [component](components.md) consumer trait, must require the context to implement it — a `where Self: SomeTrait` bound that reads as machinery. `#[uses(RectangleArea)]` instead reads as "this function uses the `RectangleArea` capability," and the macro turns each listed name into the corresponding `Self` bound on the impl so the body can call those methods directly on `self`. Building a scaled area on top of the base one: - -```rust -#[cgp_fn] -#[uses(RectangleArea)] -pub fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { - self.rectangle_area() * scale_factor * scale_factor -} -``` - -This adds `Self: RectangleArea` to the generated impl's `where` clause, alongside the `HasField` bound from the implicit `scale_factor` — the imported bound lands on the impl only, never on the trait, exactly like writing `where Self: RectangleArea` by hand. The simple `TraitIdent` form is the idiomatic one, since the attribute is meant to read like an import, but an entry may be any bound a `where` clause accepts, including an associated-type-equality binding such as `HasErrorType`. Reach for that sparingly: to pin an *abstract type*, prefer the [`#[use_type]` equality form](abstract-types.md) `#[use_type(HasErrorType.{Error = AppError})]`, which adds the bound *and* rewrites the type, over spelling the equality in `#[uses]`. When a provider imports several capabilities, list them all in one attribute separated by commas — `#[uses(CanQueryUserBalance, CanRaiseHttpError)]` — rather than stacking `#[uses(...)]` repeatedly; the combined form reads as a single dependency list. `#[uses(...)]` works in both `#[cgp_fn]` and `#[cgp_impl]`, and the imported capability may itself be defined either way. - -## Adding supertraits and trait bounds: `#[extend]` and `#[extend_where]` - -In `#[cgp_fn]`, the function's own `where` clauses are impl-side dependencies kept off the trait, so there is no place to write a supertrait by hand — `#[extend(...)]` is the only way to add one. Where `#[uses]` adds a hidden impl-side bound (the `use` equivalent), `#[extend]` promotes its bound to a *supertrait* of the generated trait — a public requirement every implementor satisfies and every caller may rely on (the `pub use` equivalent). The bound appears in two places: as a supertrait on the trait, so an associated type like `Self::Scalar` resolves and callers know the bound holds, and in the impl's `where` clause so the body can use it. The example below uses the abstract-type trait `HasScalarType` to make that two-placement behavior visible in one signature, but for an abstract-type supertrait like this, `#[use_type]` is the production form (see the note after it) and `#[extend]` is reserved for a non-type capability supertrait. A `#[cgp_fn]` over an abstract scalar type: - -```rust -pub trait HasScalarType { - type Scalar: Clone + Mul; -} - -#[cgp_fn] -#[extend(HasScalarType)] -pub fn rectangle_area( - &self, - #[implicit] width: Self::Scalar, - #[implicit] height: Self::Scalar, -) -> Self::Scalar { - width * height -} -// → pub trait RectangleArea: HasScalarType { fn rectangle_area(&self) -> Self::Scalar; } -``` - -`#[extend]` accepts the same simplified `TraitIdent` syntax as `#[uses]`, and is also usable on `#[cgp_component]`, where it is the preferred way to add a *non-type capability* supertrait: writing `#[extend(HasName)]` reads as importing a capability, whereas the native `pub trait CanGreet: HasName` syntax reads as OOP-style inheritance from a parent class, which a CGP supertrait is not. (For an abstract-type component whose associated type the signatures name, prefer `#[use_type]` instead, which adds the supertrait *and* rewrites the type — the recommended form for abstract-type components.) Its sibling `#[extend_where(...)]` adds *`where`-clause* predicates to the generated trait definition rather than supertraits, and is `#[cgp_fn]`-only. Unlike `#[uses]` and `#[extend]`, it accepts arbitrary predicates — including associated-type equality — so a generic parameter can carry a publicly visible bound: - -```rust -#[cgp_fn] -#[extend_where(Scalar: Clone)] -pub fn rectangle_area(/* … */) -> Scalar -where - Scalar: Mul, -{ /* … */ } -// → pub trait RectangleArea where Scalar: Clone { fn rectangle_area(&self) -> Scalar; } -``` - -The `Scalar: Mul` bound from the body stays an impl-side dependency, while `Scalar: Clone` from `#[extend_where]` is promoted onto the trait so any code naming `RectangleArea` can rely on it without restating it. - -## Getter traits: `#[cgp_auto_getter]` - -A getter trait exposes a context field as a reusable `self.name()` accessor, and `#[cgp_auto_getter]` generates its single blanket impl by reading the field whose name matches the method name. Use it *sparingly* — an [implicit argument](#field-access-dressed-as-a-function-argument-implicit) is the default for reading a field, since it injects the value as an ordinary-looking parameter with no separate trait to declare, reads only from the provider's own `self`, and takes a plain `&T` by reference without cloning. Because that covers every same-context read — even a field several providers each consume, written as the same implicit argument in each — a getter trait earns its keep only in the cases an implicit argument cannot reach: the field lives on a type *other* than the provider's context, so the getter is required as a `where` bound on that type (`Request: HasBasicAuthHeader`, with no `self` field to read); the accessor must exist as a *named* capability other code depends on through `#[uses(HasName)]` or a supertrait; or the getter carries an associated type inferred from the field so the type stays abstract for callers. The macro takes no arguments and re-emits the trait verbatim, adding a blanket impl over `__Context__` keyed by the method name as a `Symbol!`: - -```rust -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -// generated: -impl<__Context__> HasName for __Context__ -where - __Context__: HasField, -{ - fn name(&self) -> &str { - self.get_field(PhantomData::).as_str() - } -} -``` - -That generated blanket impl is the point of the macro, and it follows the same access rules as `#[implicit]`: a plain `&T` return reads a `T` field directly, while the `&str` shorthand reads a `String` field and appends `.as_str()`. Other shorthands include `Option<&T>` (an `Option` field via `.as_ref()`), `Option<&str>` (an `Option` field via `.as_deref()`), `&[T]` (a field implementing `AsRef<[T]>`), an owned type — path, tuple, or array — via `.clone()`, and, with a `&mut self` receiver, the mutable mirrors `&mut T`, `&mut [T]` (a field implementing `AsMut<[T]>`, via `.as_mut()`), and `Option<&mut T>` (via `.as_mut()`) — all through `get_field_mut`. A trait may declare several methods, each mapping independently to its own field — `fn width(&self) -> &f64; fn height(&self) -> &f64;` produces one `where` predicate and one body per field in the same impl. - -A single getter may also declare a local associated type and use it as its return type, which lets the abstract type be inferred from the field. The trait must then contain exactly one method returning `&Self::AssocType`; the macro lifts the type into a generic parameter on the impl and binds it through the `HasField` `Value`: - -```rust -#[cgp_auto_getter] -pub trait HasName { - type Name: Display; - fn name(&self) -> &Self::Name; -} -// → impl<__Context__, Name> HasName for __Context__ -// where Name: Display, __Context__: HasField -// { type Name = Name; … } -``` - -A context gains the getter just by deriving `HasField` with a matching field — `person.name()` resolves through the blanket impl with no wiring. The cost of that simplicity is rigidity: the field name *must* equal the method name, and there is no way to swap the implementation. When you need either, `#[cgp_getter]` provides it — but that is an advanced tool, not a routine next step. - -When the getter's return type names an abstract type that lives on a *foreign* type — a generic parameter of the trait rather than `Self` — prefer importing it with [`#[use_type]`](abstract-types.md)'s `in Context` clause over a hand-written `where` bound and a qualified path. Write - -```rust -#[cgp_auto_getter] -#[use_type(HasUserIdType.UserId in App)] -pub trait HasLoggedInUser { - fn logged_in_user(&self) -> &Option; -} -``` - -rather than declaring `where App: HasUserIdType` and returning `&Option`. The `in App` clause supplies `App: HasUserIdType` on the generated trait and rewrites the bare `UserId` to `::UserId`, so the plain `` parameter and the bare alias are enough — the same benefit `#[use_type]` gives on `Self`, extended to a parameter. - -## Wireable getters: `#[cgp_getter]` and `UseField` - -`#[cgp_getter]` defines a getter as a full CGP [component](components.md) instead of a blanket impl, so the field name can differ from the method name and the getter can be swapped per context through [wiring](wiring.md). It is a specialized, advanced tool: reserve it for when a context genuinely needs full control over which field a getter reads from, and prefer an implicit argument or `#[cgp_auto_getter]` for the ordinary case of a same-named field. It accepts the same getter-method forms as `#[cgp_auto_getter]`, but because it is an extension of `#[cgp_component]` it needs a provider trait name. The default derives one from the trait name by stripping a leading `Has` and appending `Getter`, so `HasName` yields the provider `NameGetter` and the component marker `NameGetterComponent`; pass an argument like `#[cgp_getter(GetName)]` to override it. - -The decoupling is delivered by an automatically generated `UseField` provider impl. `UseField` is a zero-sized provider (a `PhantomData`-only marker named in wiring, carrying no runtime value) that implements the getter by reading the field named `Tag` from the context — and crucially, `Tag` need not be the method name: - -```rust -#[cgp_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[derive(HasField)] -pub struct Person { - pub first_name: String, -} - -delegate_components! { - Person { - NameGetterComponent: UseField, - } -} -// person.name() now reads the first_name field -``` - -The trait method is `name` but the context stores the value in `first_name`, and the wiring `NameGetterComponent: UseField` bridges the two — the field name lives in the wiring, not in the trait. Internally `#[cgp_getter]` generates a `UseField` impl whose tag is left as a free parameter, in contrast to the `#[cgp_auto_getter]` blanket impl that hard-codes the tag to the method name. The macro also emits a `UseFields` provider (the provider-side analogue of the auto-getter blanket impl, keyed by method name) and, for single-method getters, a `WithProvider` adapter. - -For getters whose return type is reached *through* a field by `AsRef`/`AsMut` rather than being the field itself, the related `UseFieldRef` provider reads the field at `Tag` and calls `as_ref()` to expose `&Value` — for example `UseFieldRef` exposes `&str` from a `String` field. It decouples the exposed type from the stored type as well as the field name from the method name. Unlike `UseField`, it is not re-exported through the prelude; reach it through `cgp::core::field::impls`. - -`UseField` and `UseFieldRef` are the foundational field-getter providers; their `WithProvider` aliases `WithField` and `WithFieldRef` (see [wiring](wiring.md)) are what you often see wired directly onto a getter component, reading `NameGetterComponent: WithField` as "serve this getter from the named field." - -## Reaching a nested field with `ChainGetters` - -When the value a getter needs is not on the context but several hops inside it — the context holds a config, the config holds a connection, the connection holds the timeout — `ChainGetters` walks the path. It takes a `Product!` list of getters and applies them in order, threading the reference each step produces into the next, so the chain reads like the path it traverses: - -```rust -delegate_components! { - App { - TimeoutGetterComponent: ChainGetters Config - GetConnection, // Config -> Connection - GetTimeout, // Connection -> Duration - ]>, - } -} -``` - -Each element is itself a field getter for the value the previous step produced, and like every provider `ChainGetters` is a zero-sized marker named in wiring. It saves writing a bespoke provider that hand-walks the nesting whenever a getter must reach into a sub-context. - -## Getters are just traits: explicit implementation - -Every getter the macros produce is an ordinary trait, and explicit implementation is always available — the macros only save boilerplate. This matters when a context does not derive `HasField`, or stores the value under a name no tag matches. Because `#[cgp_auto_getter]` adds only a blanket impl and `#[cgp_getter]` a component whose consumer trait is plain Rust, you can write the impl by hand on a concrete type: - -```rust -pub struct Person { - pub full_name: String, -} - -impl HasName for Person { - fn name(&self) -> &str { - &self.full_name - } -} -``` - -The explicit form is more verbose but requires no understanding of `HasField` or blanket impls — a reminder that the whole apparatus on this page is convenience layered over vanilla Rust traits. - -## Choosing between the constructs - -For reading a field into a provider — the common case — an `#[implicit]` argument is the default: it keeps the access local and the code reading like a plain function, whether the value is used once or throughout the body, and it applies to any field on the provider's own context, even one several providers each read. Promote a field to a getter trait only when an implicit argument cannot reach it — the field lives on another type and the getter is a `where` bound on it, the accessor must be a named capability other code depends on, or it carries an associated type inferred from the field — and then use `#[cgp_auto_getter]` when the field name matches the method name (the usual getter), and `#[cgp_getter]` (with `UseField`) as the advanced tool reserved for when the source field name must be chosen per context at wiring time. For a whole capability rather than a single field, `#[cgp_fn]` defines one with no wiring when a single implementation suffices, and a full [component](components.md) when many providers must coexist. All of them rest on the same `HasField` machinery and the same access rules, so mixing them carries no conceptual overhead. - -## Further reference - -Online docs (current v0.8.0): [`#[cgp_fn]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_fn.md), [`HasField`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/has_field.md), [`#[derive(HasField)]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/derives/derive_has_field.md), [`#[implicit]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/implicit.md), [`#[uses]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/uses.md), [`#[extend]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/extend.md), [`#[extend_where]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/extend_where.md), [`#[cgp_auto_getter]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_auto_getter.md), [`#[cgp_getter]`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_getter.md), [`UseField`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/use_field.md), [`UseFieldRef`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/use_field_ref.md), and the [implicit-arguments concept](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/implicit-arguments.md). diff --git a/docs/skills/cgp/references/handlers.md b/docs/skills/cgp/references/handlers.md deleted file mode 100644 index 88f02ab5..00000000 --- a/docs/skills/cgp/references/handlers.md +++ /dev/null @@ -1,250 +0,0 @@ -# Handlers - -The handler family is a spectrum of CGP components that all transform an `Input` into an `Output` under a phantom `Code` tag, varying along three axes — synchronous vs. async, infallible vs. fallible, and input vs. no-input — together with the macros that build handlers from functions, the combinators that compose and lift them, the dispatchers that route over extensible data, and the monads that chain them. - -Assume `use cgp::prelude::*;` throughout; the CGP version is v0.8.0. Every handler is an ordinary [component](components.md) — a consumer trait the context calls, a provider trait a zero-sized provider implements, and a `…Component` marker that [wiring](wiring.md) maps to a provider — so nothing here needs machinery beyond what you already know. - -## The shared shape and the three axes - -Every handler maps `(context, Code, Input) -> Output`, where `Code` is a phantom tag carried as `PhantomData` and `Output` is an *associated type* the provider chooses, not a parameter the caller fixes. The `Code` tag holds no data; it exists so one context can host many handlers keyed by distinct tags, and so wiring can dispatch on it. The family is large because real computations differ along three independent axes, and each combination gets its own component so a provider declares exactly the capabilities it has. - -The first axis is **synchronous vs. async**: an async component returns a future and its method is `async`, marked by `Async` in the name (`AsyncComputer` is the async `Computer`). The second is **infallible vs. fallible**: a fallible component returns `Result` against the context's abstract error and so supertraits [`HasErrorType`](abstract-types.md), marked by the `Try` prefix. The third is **owned vs. by-reference input**: every base component has a `*Ref` sibling that borrows `&Input` instead of consuming `Input`. The general principle is that you write the *weakest* variant that fits and let combinators promote it upward, because an infallible computation is trivially fallible, a sync one trivially async, and an owned-input one can serve a borrow — but never the reverse. - -The components, by corner, are these. `Computer`/`CanCompute` is the pure-computation base — synchronous and infallible, with `AsyncComputer` and the `*Ref` variants alongside it. `TryComputer`/`CanTryCompute` is fallible but synchronous. `Handler`/`CanHandle` is the fully general async-and-fallible computation, the bound a generic consumer targets because every simpler provider promotes up to it. `Producer`/`CanProduce` sits apart as the no-input case, producing a value from the context and `Code` alone. A related pair, `CanRun`/`CanSendRun`, runs a named task rather than transforming a value. - -## The computation components - -`CanCompute` is the simplest member and the one to reach for first. Its method takes the `Code` tag and the input by value and returns the chosen `Output` with no failure path: - -```rust -#[cgp_component(Computer)] -#[derive_delegate(UseDelegate)] -#[derive_delegate(UseInputDelegate)] -pub trait CanCompute { - type Output; - fn compute(&self, _code: PhantomData, input: Input) -> Self::Output; -} -``` - -The provider trait `Computer` moves the context into an explicit first parameter, as for any component; the marker is `ComputerComponent`. The two `#[derive_delegate]` directives let a context route to different providers by `Code` (via `UseDelegate`) or by `Input` type (via the family's `UseInputDelegate`), the basis of [dispatching](#dispatching-over-extensible-data). `AsyncComputer`/`CanComputeAsync` is identical but declares `async fn compute_async` under `#[async_trait]`, and `ComputerRef`/`AsyncComputerRef` borrow the input as `&Input`. None of the four supertrait `HasErrorType`, because none can fail. - -`CanTryCompute` adds the failure path. It supertraits `HasErrorType` so its `Result` can name the context's abstract error, which is what keeps a provider generic over the error backend: - -```rust -#[cgp_component(TryComputer)] -#[derive_delegate(UseDelegate)] -#[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanTryCompute { - type Output; - fn try_compute(&self, _code: PhantomData, input: Input) - -> Result; -} -``` - -A `TryComputer` provider carries a `Context: HasErrorType` bound and typically converts a concrete source error into the abstract one with [`CanRaiseError`](error-handling.md). `TryComputerRef` is the by-reference sibling. - -`CanHandle` is the general corner — async *and* fallible — and is the bound generic pipeline code targets, since `Computer`, `AsyncComputer`, and `TryComputer` all promote up to it: - -```rust -#[async_trait] -#[cgp_component(Handler)] -#[derive_delegate(UseDelegate)] -#[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanHandle { - type Output; - async fn handle(&self, _tag: PhantomData, input: Input) - -> Result; -} -``` - -A function bounded by `Context: CanHandle` accepts any wired computation regardless of which capabilities the underlying provider actually uses. `HandlerRef` borrows the input. - -`CanProduce` is the no-input case: it threads no `Input`, only the context and a `Code` tag, and so carries a single `#[derive_delegate(UseDelegate)]` with no `UseInputDelegate` counterpart and does not supertrait `HasErrorType`: - -```rust -#[cgp_component(Producer)] -#[derive_delegate(UseDelegate)] -pub trait CanProduce { - type Output; - fn produce(&self, _code: PhantomData) -> Self::Output; -} -``` - -A producer is the natural source of values that flow into a pipeline — a constant or a context-derived default — and promotes into any input-taking handler by ignoring the supplied input. - -A handler provider is an ordinary zero-sized provider implementing the provider trait for a generic context. The built-in `UseField` is a `Computer` that forwards the computation to the value held in the context's `Tag` field; beyond it, the combinators below supply the rest. - -## Task runners: `CanRun` and `CanSendRun` - -The runner pair executes a unit of work selected by a `Code` tag rather than transforming a value, completing to `Result<(), Error>`. `CanRun` is the base async form; `CanSendRun` exists to recover a `Send` future for spawning: - -```rust -#[cgp_component(Runner)] -#[async_trait] -#[derive_delegate(UseDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanRun { - async fn run(&self, _code: PhantomData) -> Result<(), Error>; -} - -#[cgp_component(SendRunner)] -#[async_trait] -#[derive_delegate(UseDelegate)] -#[use_type(HasErrorType.Error)] -pub trait CanSendRun { - fn send_run(&self, _code: PhantomData) - -> impl Future> + Send; -} -``` - -A context hosts many tasks by wiring `RunnerComponent` to a `UseDelegate` table keyed on `Code`, so `app.run(PhantomData::)` and `app.run(PhantomData::)` reach distinct providers. A runner provider reaches the runtime through `HasRuntime` to spawn or await work; the `CanSendRun` variant is the mechanism for the `Send`-recovery pattern described under [recovering `Send` bounds](#recovering-send-bounds). - -## The runtime: `HasRuntimeType` and `HasRuntime` - -A runner needs something to run *against* — an executor that can spawn, sleep, open sockets, or read the clock — and CGP keeps that runtime abstract so the same code runs on Tokio in production, a mock in tests, or a single-threaded executor in a benchmark. Two built-in components express it, split because the two questions are independent. `HasRuntimeType` is an [abstract type](abstract-types.md) (`type Runtime`) answering *what* the runtime type is, chosen per context through wiring like any `#[cgp_type]` component. `HasRuntime` is a getter that supertraits `HasRuntimeType` and answers *how to obtain* the runtime value from a borrow of the context (`fn runtime(&self) -> &Self::Runtime`). Code that only names types the runtime exposes bounds on `HasRuntimeType` alone; code that performs effects bounds on `HasRuntime`. This pair is the seam where context-generic logic meets concrete async machinery, which is why the runner providers reach through it to do their work. - -## Defining handlers from functions - -`#[cgp_computer]` turns a plain function into a `Computer` provider and wires the rest of the family by promotion, so a computation as small as "add two numbers" needs no hand-written plumbing: - -```rust -#[cgp_computer] -fn add(a: u64, b: u64) -> u64 { - a + b -} -``` - -The function name becomes the provider name in PascalCase (`Add`), unless an explicit name is given as `#[cgp_computer(MyAdder)]`. The parameters are collected into a tuple that becomes the single `Input` type and destructured back inside the generated method; the return type becomes `Output`. The macro emits the function unchanged, a `#[cgp_new_provider]` impl of the base trait, and a `delegate_components!` block routing every other handler component to a promotion bundle: - -```rust -#[cgp_new_provider] -impl<__Context__, __Code__> Computer<__Context__, __Code__, (u64, u64)> for Add { - type Output = u64; - fn compute(_context: &__Context__, _code: PhantomData<__Code__>, - (arg_0, arg_1): (u64, u64)) -> Self::Output { - add(arg_0, arg_1) - } -} -// delegate_components! routes the rest of the family to PromoteComputer -``` - -The base trait and bundle are chosen from two axes the macro reads off the signature. A synchronous plain-value function uses `Computer` + `PromoteComputer`; a synchronous `Result`-returning one keeps `Computer` as the base (its `Output` is the `Result`) but uses `PromoteTryComputer`, which surfaces the `Ok`/`Err` as genuine success/failure; an `async` plain-value function uses `AsyncComputer` + `PromoteAsyncComputer`; an `async` `Result` function uses `AsyncComputer` + `PromoteHandler`. Generic parameters and `where` clauses carry onto the impl; a `&Value` parameter makes the input tuple borrow and the bundle's `PromoteRef` entries serve the `*Ref` components. The result is that one `add` answers `compute`, `try_compute`, `compute_async`, and `handle`. - -`#[cgp_producer]` is the input-less sibling, turning a zero-argument function into a `Producer` and wiring `PromoteProducer` across the whole family: - -```rust -#[cgp_producer] -fn magic_number() -> u64 { - 42 -} -``` - -The function must take no parameters, must not be `async`, and must have no generics — a producer's shape is fixed, so there is no variation in the expansion. The generated `MagicNumber` answers `produce` and, because `PromoteProducer` discards the input that each computer slot supplies, also `compute`, `try_compute`, `handle`, and the `*Ref` forms, every one yielding `42`. - -## Composing and promoting with handler combinators - -The handler combinators are zero-sized providers of `cgp-handler` that build, sequence, and lift handlers, carrying their inner providers in `PhantomData`. They divide into composition, the identity element, and promotion. - -`ComposeHandlers` runs two handlers back to back, pinning the second's input to the first's output and exposing the pair as a member of every handler family; the fallible variants `?`-short-circuit and the async variants `.await` each step. `PipeHandlers` generalizes it to a `Product![...]` list, folding right to left so `PipeHandlers` behaves as `ComposeHandlers>` and serves whichever handler shape the wiring asks for, provided every stage supports it: - -```rust -delegate_components! { - MyContext { - ComputerComponent: - PipeHandlers, - Add, - Multiply, - ]>, - } -} -// input 5 over foo=2, bar=3, baz=4 -> ((5 * 2) + 3) * 4 -``` - -`ReturnInput` is the identity handler — it ignores the context and `Code` and returns its input unchanged (wrapped in `Ok` for the fallible variants) — and is the neutral element of composition, useful as a placeholder stage. - -The promotion combinators each take one inner provider and re-expose it under a different, more capable family member, encoding the one-directional lifts the axes permit. `Promote` lifts upward without adding behavior: a `Producer` into a `Computer` (ignoring the input), a `Computer` into a `TryComputer` (wrapping in `Ok`), an `AsyncComputer` into a `Handler`. `PromoteAsync` lifts a sync provider into an async one whose future is immediately ready. `PromoteRef` bridges value and reference handlers by dereferencing or re-borrowing. `TryPromote` bridges a `Result`-valued `Computer` and a genuine `TryComputer` in both directions. You rarely name these one at a time; instead the *promotion bundles* — `PromoteComputer`, `PromoteTryComputer`, `PromoteProducer`, `PromoteAsyncComputer`, `PromoteHandler` — are `delegate_components!` tables that wire a whole cluster of components to the right single-step promoter from a given base. These are exactly what `#[cgp_computer]` and `#[cgp_producer]` wire for you, and reaching for `PromoteComputer` by hand achieves the same when wiring explicitly. - -## Dispatching over extensible data - -Dispatching routes an [extensible-data](extensible-data.md) value to per-variant or per-field handlers: for an enum, match the current variant and run its handler; for a record, run a handler per field to build it. It keeps the per-variant/per-field structure of a concrete `match` or struct literal but lets the shape and handlers be chosen by type, so the same matcher serves many enums. - -`#[cgp_auto_dispatch]` is the highest-level entry point. Written above a trait that already has one impl per payload type, it generates a blanket impl of that trait for any extensible enum of those types, dispatching each variant to that payload's impl: - -```rust -#[cgp_auto_dispatch] -pub trait HasArea { - fn area(&self) -> f64; -} -// with impls for Circle and Rectangle, and a #[derive(CgpData)] enum Shape, -// HasArea is now implemented for Shape too: -let shape = Shape::Rectangle(Rectangle { width: 2.0, height: 2.0 }); -assert_eq!(shape.area(), 4.0); -``` - -For each method it emits a per-variant computer via `#[cgp_computer]` (named `Compute` plus the method name) and an enum-level impl that runs the appropriate value-handler matcher — `MatchWithValueHandlers` for `&self`, its `Mut` form for `&mut self`, and the `MatchFirstWith…` family when the method takes extra arguments, with the `Async` form for `async` methods. A method may not have non-lifetime generic parameters, since the generated impl would need a quantified bound Rust lacks; such a method must use the combinators directly. - -Underneath, the dispatch combinators of `cgp-dispatch` express both directions as handler providers. On the matching side, `MatchWithHandlers` (with `Ref`/`Mut` and `MatchFirstWith…` forms) converts the input to its extractor and runs a list of per-variant adapters — `ExtractFieldAndHandle` tries one variant and forwards its payload, `HandleFieldValue` strips the `Field` wrapper to hand the bare value to a computer, and `DowncastAndHandle` matches a group of variants to a sub-matcher. The list runs as a monadic pipeline that short-circuits on the first match and proves exhaustiveness without a wildcard, because each miss rules out one variant until the final remainder is uninhabited. `MatchWithValueHandlers` builds that list automatically from the enum's own field list. Dispatch on the *input* type uses the `Computer` component's `UseInputDelegate` directive, wired as a nested table with one entry per input type: - -```rust -delegate_components! { - App { - ComputerComponent: - UseInputDelegate, - } -} -``` - -Each entry routes one input type (the array form sharing a provider across several), so a `Circle` or `Rectangle` input reaches `ComputeArea` while a whole `Shape` enum reaches the matcher. This input-type dispatch keys on the `Input` parameter through the component's `UseInputDelegate` directive, which is why it is written as a nested table rather than through the `open` statement — `open` rides the default `RedirectLookup` that keys on the primary `Code` parameter (see [wiring](wiring.md) for the `open` form, which is the modern way to do `Code`-keyed dispatch). The nested-table form is the established way to dispatch on the input type, and this concerns only the *wiring* — the dispatch combinators above are unaffected. - -On the building side, `BuildWithHandlers` starts from an empty builder, pipes it through per-field adapters — `BuildAndSetField` computes and sets one field, `BuildAndMerge` merges a whole record's shared fields — and finalizes. Because finalization is available only when every field is present, omitting a handler for a field is a compile error rather than a runtime half-built value. - -## Composing through a monad - -Plain composition feeds each output straight into the next step, which is wrong the moment a step can produce a value meaning "stop here." Monadic handlers solve this: the monad decides which branch of a step's output threads forward and which short-circuits out as the final result. `PipeMonadic` is the entry point — a monad marker plus a `Product![...]` list — and the pipeline it builds is itself a `Computer`-family provider: - -```rust -PipeMonadic::::compute(&context, code, 253) -// 253 -> Ok(254) -> Ok(255) -> Err("overflow"); the third overflow becomes the output -``` - -CGP ships three monad markers. `IdentMonadic` threads every value forward and never short-circuits, recovering plain `PipeHandlers` composition. `ErrMonadic` short-circuits on `Err` and continues on `Ok` — the `?`-style early return where the first error wins. `OkMonadic` is the mirror, stopping at the first `Ok`, which suits retry-until-success and is what the dispatch matcher loop runs under. The two `Result`-branching markers have transformer forms, `OkMonadicTrans` and `ErrMonadicTrans`, that stack their behavior on a base monad so a pipeline over a nested `Result, F>` can short-circuit on the outer error while threading the inner result. - -The per-step providers `BindOk` and `BindErr` implement a single bind and are what `PipeMonadic` composes internally; they can also be dropped into a `PipeHandlers` list directly for step-by-step control. `BindErr` runs `Cont` on an `Ok` payload and short-circuits an `Err`; `BindOk` is its mirror. `PipeMonadic` also implements the fallible and async-fallible components by demoting each provider through `TryPromote`, applying `ErrMonadic` as a transformer over `M`, and re-wrapping — so a monadic pipeline reached through `try_compute` or `handle` short-circuits on the context's error type as well. - -## Recovering `Send` bounds - -An async trait method advertises a future whose auto-traits the caller cannot name. The `#[async_trait]` rewrite turns `async fn handle(..)` into `fn handle(..) -> impl Future<..>` with no boxing and no `Send`, which is faithful and zero-cost but drops the `Send` guarantee. The bound becomes load-bearing when the future is spawned onto a work-stealing executor (the default Tokio runtime an Axum server uses), which may migrate a suspended task between threads. The clause you want — "the future of `handle` is `Send` for any arguments" — is Return Type Notation (`handle(..): Send`), which stable Rust does not yet offer. - -The workaround is a second, ordinary trait whose method states `+ Send` directly in its return type, sidestepping RTN: - -```rust -pub trait CanHandleApiSend: - CanHandleApi + Send + Sync -{ - fn handle_api_send(&self, _api: PhantomData, request: Self::Request) - -> impl Future> + Send; -} -``` - -This is a plain trait, not a component — it adds nothing to the wiring and exists only to carry the stronger bound. It cannot be implemented with a single generic blanket impl, because the body wraps `self.handle_api(..)` in an `async` block whose `Send`-ness depends on the opaque future it awaits — the same gap restated, just RTN in disguise. The impl must therefore be written per *concrete* `(context, API)` pair, where `self.handle_api(api, request)` resolves through the wiring to a concrete future whose auto-traits the compiler computes structurally and finds `Send`: - -```rust -impl CanHandleApiSend for MockApp { - async fn handle_api_send(&self, api: PhantomData, request: Self::Request) - -> Result { - self.handle_api(api, request).await - } -} -``` - -Each impl is mechanical forwarding, yet each is also a proof accepted only because the future really is `Send` at that instantiation. One concrete impl per API per context replaces the single generic impl RTN would have allowed — the cost of the missing notation. The built-in `CanSendRun` runner applies the same pattern as a `SendRunner` proxy on the concrete context, letting a spawning runner provider clone the context into a `Send` future without `Send` bounds leaking into any abstract interface. - -## Further reference - -Online docs: [concepts/handlers.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/handlers.md), [concepts/dispatching.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/dispatching.md), [concepts/monadic-handlers.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/monadic-handlers.md), [concepts/send-bounds.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/send-bounds.md); reference docs [components/computer.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/computer.md), [components/try_computer.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/try_computer.md), [components/handler.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/handler.md), [components/producer.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/producer.md), [components/runner.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/components/runner.md), [macros/cgp_computer.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_computer.md), [macros/cgp_producer.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_producer.md), [macros/cgp_auto_dispatch.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_auto_dispatch.md), [providers/handler_combinators.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/handler_combinators.md), [providers/dispatch_combinators.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/dispatch_combinators.md), [providers/monad_providers.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/monad_providers.md). diff --git a/docs/skills/cgp/references/higher-order-providers.md b/docs/skills/cgp/references/higher-order-providers.md deleted file mode 100644 index 5224ebe7..00000000 --- a/docs/skills/cgp/references/higher-order-providers.md +++ /dev/null @@ -1,150 +0,0 @@ -# Higher-order providers - -How one provider takes another provider as a generic parameter and builds on whatever that inner provider computes — the type-level counterpart to passing a function to a function. - -A higher-order provider is a [provider](components.md) parameterized by another provider, so part of its behavior is supplied by an inner provider it delegates to rather than fixed in its own code. The motivating shape is a wrapper that transforms the output of an existing implementation: a `ScaledArea` that multiplies whatever area an inner calculator produces, an `IterSumArea` that sums the areas an inner calculator returns over a collection, a `GloballyScaledArea` that applies a context-wide factor on top of a base calculation. In each case the outer provider knows the *transformation* but not the *base case* — the base case is a parameter. Because a provider carries no runtime value (it is a zero-sized marker naming an implementation), nesting providers costs nothing: the composition happens entirely in types. Assume `use cgp::prelude::*;` throughout; the CGP version is v0.8.0. - -## The shape, and the stray `` - -A higher-order provider is written like any other provider with `#[cgp_impl]`, with the inner provider declared as a generic parameter in the provider's `Self` position and bound to a *provider trait* in the `where` clause. The detail that makes it look stranger than it is the extra `` on that inner bound. Recall that a [provider trait](components.md) moves the consumer trait's `Self` into an explicit leading `Context` parameter — `AreaCalculator`, not the consumer trait's `CanCalculateArea`. So when the outer provider depends on its inner provider, the bound must name that context argument explicitly: - -```rust -#[cgp_impl(new ScaledArea)] -impl AreaCalculator -where - InnerCalculator: AreaCalculator, -{ - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - InnerCalculator::area(self) * scale_factor * scale_factor - } -} -``` - -The `` in `InnerCalculator: AreaCalculator` is exactly that leading context slot, filled with the context the outer provider is implementing for. Inside `#[cgp_impl]`, `self`/`Self` mean the *context*, so `` is the context type. The same asymmetry shows at the call site: the inner provider is invoked as the associated function `InnerCalculator::area(self)`, not as a method `self.area()`, because the provider trait's method takes the context as an explicit first argument. A reader who thinks of a provider as "just the consumer trait implemented elsewhere" is caught off guard by both the bound and the call. - -## Hiding the friction with `#[use_provider]` - -The `#[use_provider]` attribute exists to erase the bound surprise, and is the idiomatic way to write higher-order providers — always reach for it, since it preserves the illusion that a provider trait has the same shape as the consumer trait it came from. Written alongside `#[cgp_impl]`, it takes the inner bound *without* the context argument and fills the `` back in for you: - -```rust -#[cgp_impl(new ScaledArea)] -#[use_provider(InnerCalculator: AreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] scale_factor: f64) -> f64 { - let base_area = InnerCalculator::area(self); - base_area * scale_factor * scale_factor - } -} -``` - -The author writes `InnerCalculator: AreaCalculator` and the macro inserts the context type at index 0 of the trait's generic arguments, emitting `InnerCalculator: AreaCalculator` into the `where` clause — the two snippets above are equivalent. The shape it parses is `Provider: Trait`: a provider type, a colon, and one or more provider-trait bounds joined by `+`; the trait may carry further generic arguments after the context slot, preserved in order behind the inserted `Self`. To bind several inner providers, list them all in one attribute separated by commas — `#[use_provider(A: TraitA, B: TraitB)]` — rather than stacking a separate attribute per provider; the combined form reads as a single dependency list, and stacked attributes behave identically but are for when a real reason calls for it. The same attribute works on `#[cgp_fn]`. - -What `#[use_provider]` does *not* do is rewrite the body. It completes the bound only; the inner provider is still invoked as the associated function `InnerCalculator::area(self)`, with `self` passed as the explicit context. There is no call-site form — a bare `#[use_provider(InnerCalculator)]` on an expression is not accepted, and no pass turns `receiver.method(args)` into `Provider::method(receiver, args)`. The body must spell the associated-function call out itself. Calling the inner provider as a method (`self.area()`) would instead route through whatever provider the context has wired for the component, a different dispatch and usually not what a higher-order provider wants. - -## `UseContext` as a default inner provider - -A higher-order provider can default its inner provider to [`UseContext`](wiring.md) so that, absent an explicit choice, it falls back to whatever the context itself is already wired to. This requires giving the provider an explicit struct definition with a default generic parameter: - -```rust -pub struct IterSumArea(pub PhantomData); -``` - -`UseContext` is the provider that implements any provider trait by deferring to the context's own consumer-trait implementation — the dual of the consumer blanket impl. With `IterSumArea` as the default, an unparameterized `IterSumArea` computes each inner element through the context's existing area wiring, so the wrapper drops in without restating the base case. Overriding the parameter — `IterSumArea` — instead binds the inner behavior statically, bypassing the context's wiring, which is useful for overriding or short-circuiting what the context would otherwise resolve. This default exists *only* when the provider has an explicit struct carrying the default parameter; a provider defined purely through `#[cgp_impl(new ...)]` has no default and must always have its inner provider named. - -## Not every generic provider is higher-order - -A provider that merely has generic parameters is not a higher-order provider; it becomes one only when a parameter is constrained to implement a provider trait. Many providers are generic for unrelated reasons. The `UseField`-style getter provider is generic over a field tag: - -```rust -#[cgp_impl(new GetName)] -impl NameGetter -where - Self: HasField, -{ - fn name(&self) -> &str { - self.get_field(PhantomData) - } -} -``` - -Here `Tag` is a type-level field name used only as a `HasField` key, with no provider-trait bound — so `GetName` is an ordinary parameterized provider, not a higher-order one. The defining trait of a higher-order provider is that a generic parameter appears in a *provider-trait* bound and is invoked to do part of the work, as `InnerCalculator: AreaCalculator` does. When that bound is absent, `#[use_provider]` has nothing to fill in and none of the machinery applies. - -## Generic-parameter CGP traits - -A component's trait may itself carry generic parameters, and `#[cgp_component]` appends them after the context slot. Declaring the area component to dispatch on a `Shape`: - -```rust -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self, shape: &Shape) -> f64; -} -``` - -generates the provider trait `AreaCalculator` — the original trait's parameters land *after* the leading `Context`, in declaration order. The `IsProviderFor` supertrait that every provider trait carries groups all those extra parameters into a single tuple in its `Params` slot (`()` when there are none), and any lifetime parameters are lifted into the `Life<'a>` type rather than appearing as bare lifetimes. A provider then writes `impl AreaCalculator for RectangleArea`, naming the concrete `Shape` it handles. - -## Dispatching on a generic parameter with `open` - -When the right provider depends on *which* concrete type a generic parameter is — `Rectangle` handled by one provider, `Circle` by another — the choice is made by a second lookup keyed on that parameter rather than on the component marker. The modern way a context writes this per-type dispatch is the `open` statement of `delegate_components!`, which folds the per-value entries directly into the context's own table. Given a `CanCalculateArea` consumer trait whose `Shape` parameter selects the area formula, a context opens the component and then assigns a provider per shape: - -```rust -delegate_components! { - MyApp { - open AreaCalculatorComponent; - - @AreaCalculatorComponent.Rectangle: RectangleArea, - @AreaCalculatorComponent.Circle: CircleArea, - } -} -``` - -The leading `open AreaCalculatorComponent;` header opens the component for per-value wiring, and each `@AreaCalculatorComponent.Key: Provider` entry assigns a provider for one value of the dispatch parameter: when `Shape` is `Rectangle`, `MyApp` calculates area through `RectangleArea`, and `Circle` resolves to `CircleArea`. After this wiring, `MyApp` implements `CanCalculateArea` through `RectangleArea` and `CanCalculateArea` through `CircleArea`, with the dispatch parameter selecting between them. Adding a shape is one more entry; the providers stay untouched. The `open` form needs no extra macro on the component — it rides the `RedirectLookup` impl that every `#[cgp_component]` already generates, so dispatching a component per type requires no `#[derive_delegate]` on the trait. Two shorthands keep the entries compact: an array on the final path segment shares one provider across several values (`@AreaCalculatorComponent.[Rectangle, Circle]: SomeProvider`), and generic parameters precede the dispatch value when it needs them (`@AreaCalculatorComponent.<'a, T> &'a T: SomeProvider`). The [wiring](wiring.md) reference is the canonical home of the `open` statement and its grammar. - -### Legacy: `derive_delegate` and `UseDelegate` nested tables - -An older form generates a `UseDelegate` provider and wires the per-type entries into a separate table it points at. The `#[derive_delegate(...)]` attribute asks `#[cgp_component]` to generate that provider: - -```rust -#[cgp_component(AreaCalculator)] -#[derive_delegate(UseDelegate)] -pub trait CanCalculateArea { - fn area(&self, shape: &Shape) -> f64; -} -``` - -`UseDelegate` is a zero-sized provider that treats its `Components` type as a type-level table — a `DelegateComponent` table keyed on the named parameter (`Shape`) — and forwards each method to the delegate that table maps the concrete type to. A context wires it through a nested table inside `delegate_components!`, building the outer entry and the inner lookup in one place: - -```rust -delegate_components! { - MyApp { - AreaCalculatorComponent: - UseDelegate, - } -} -``` - -This reads in two layers: `MyApp` delegates `AreaCalculatorComponent` to `UseDelegate`, and the inner table — defined inline by `new` — maps `Rectangle` to `RectangleArea` and `Circle` to `CircleArea`. The end effect matches the `open` example above, but the dispatch values live in a named side table reached through `UseDelegate` instead of in `MyApp`'s table directly. This is a legacy dispatch mechanism, retained for compatibility and expected to be deprecated; prefer `open` for new code, but expect to still encounter the `derive_delegate`/`UseDelegate` form when reading existing wiring (see [wiring](wiring.md) for both forms side by side). - -A component may dispatch on more than one parameter by stacking several `#[derive_delegate(...)]` attributes, one per dispatcher. Writing `#[derive_delegate(UseDelegate)]` and `#[derive_delegate(UseInputDelegate)]` generates one impl per dispatcher — the default `UseDelegate` keying on `Code`, plus a user-defined `UseInputDelegate` (an ordinary struct of the same single-parameter shape) keying on `Input` — so each parameter is looked up through its own table. Only the parameter named in a dispatcher's angle brackets is used as the key; the others flow through. Per-type dispatch and higher-order providers compose either way: an `open` entry or a nested delegation table can map each shape to a different `ScaledArea<...>`. - -## Cross-context dependencies through a shared context - -The real leverage of generic-parameter dispatch appears when the main target of a capability is itself a generic parameter, and a supertrait constrains the *context* rather than each parameter type. Consider an area component whose result type is an abstract scalar the context supplies: - -```rust -#[cgp_component(AreaCalculator)] -#[use_type(HasScalarType.Scalar)] -pub trait CanCalculateAreaOfShape { - fn area_of_shape(&self, shape: &Shape) -> Scalar; -} -``` - -Because the shared capability — `HasScalarType` and any value-level injection — lives on the common context, the individual shape types (`Rectangle`, `Circle`) need not implement it themselves; they only know their own geometry. The context provides the shared abstract type ([abstract types](abstract-types.md)): one app might wire `Scalar` to `f64`, another to a fixed-point type, and every shape provider produces that scalar. The context also provides value-level injection — a `GloballyScaledArea` provider can read a global scale factor from a field on the context through an implicit argument and multiply every shape's area by it, so the scale is configured once per context rather than per shape. Most importantly, provider binding is *lazy and per-context*: a `BaseApp` and a `ScaledApp` can wire the very same `Shape` to different providers — `BaseApp` opening `AreaOfShapeCalculatorComponent` and mapping `@AreaOfShapeCalculatorComponent.Rectangle: RectangleArea` to a plain `RectangleArea`, `ScaledApp` mapping `@AreaOfShapeCalculatorComponent.Rectangle: GloballyScaledArea` instead — so the same generic capability resolves to different behavior in each context without either app's shapes knowing about the other. Each context writes its per-shape choices with the `open` statement detailed in [wiring](wiring.md); the legacy `derive_delegate`/`UseDelegate` nested-table form wires the same dispatch and still appears in existing code. Each layer of such nesting can be verified independently with the `#[check_providers]` form of `check_components!`, which is what makes higher-order wiring debuggable; see [checking](checking.md). - -## Related constructs - -Higher-order providers build on the consumer/provider split and the `IsProviderFor` propagation described in [components](components.md), and they are connected to contexts through [wiring](wiring.md), where `UseContext`, the `open` statement, and the legacy nested-table `UseDelegate` forms all live. `UseContext` lets the inner provider fall back to the context's own consumer-trait impl; the `open` statement dispatches a component on a generic parameter by folding per-value entries into the context's own table, with the legacy `UseDelegate` provider (generated by the `derive_delegate` option) doing the same through a separate side table; the abstract result types that make cross-context dependencies work are covered in [abstract types](abstract-types.md) and the type-level keys in [type-level primitives](type-level-primitives.md). Each layer of a nested provider is checked independently through [checking](checking.md). - -Further reference (online docs): [concepts/higher-order-providers.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/higher-order-providers.md), [reference/attributes/use_provider.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/use_provider.md), [reference/providers/use_delegate.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/use_delegate.md), [reference/attributes/derive_delegate.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/attributes/derive_delegate.md). diff --git a/docs/skills/cgp/references/macro-grammar.md b/docs/skills/cgp/references/macro-grammar.md deleted file mode 100644 index dcc0ae52..00000000 --- a/docs/skills/cgp/references/macro-grammar.md +++ /dev/null @@ -1,323 +0,0 @@ -# Macro grammar and expansion rules - -The grammar every CGP macro parses, the code each one expands to, and how to read the compiler errors that result — the reference for writing CGP that parses, and for decoding what goes wrong when it does not. - -CGP is almost entirely procedural macros, so "valid CGP code" means two separate things: input the macro's own parser accepts, and output that expands to Rust the compiler accepts. This file covers both. It gives the formal grammar of each macro's custom syntax, states the invariant each expansion preserves, and closes with a decoder for the error messages the two layers produce. Read it to check a form you are unsure about, to predict what a macro emits, or to trace an error back to its cause. Assume `use cgp::prelude::*;` throughout; the CGP version is v0.8.0. - -## How to read the grammars - -The grammars use the notation of the [Rust Reference](https://doc.rust-lang.org/nightly/reference/notation.html), in production-rule form. A production is `Name -> Expression`, one per line inside an `ebnf` block. `x?` is optional, `x*` is zero-or-more, `x+` is one-or-more, `A | B` is a choice, `( … )` groups, and juxtaposition is sequence. A terminal is written in backticks (`` `new` ``, `` `:` ``, `` `@` ``); a nonterminal is a CamelCase name. - -A CamelCase name this file does not define is a production of the **Rust grammar**, reused rather than re-specified — `Type`, `Generics` (the `< … >` parameter list), `GenericArgs` (the `< … >` argument list), `WhereClause`, `TypePath`, and `Expression`. An `ALL_CAPS` name is a Rust lexer token: `IDENTIFIER` is a Rust identifier and `STRING_LITERAL` a string literal. Each grammar describes only the tokens the macro itself parses — the argument tokens inside an attribute's `#[name(…)]` or `#[name{…}]` delimiters, or the body tokens inside a function-like macro's `name!{ … }` — never the delimiters themselves, and never the plain Rust item an attribute is applied to. - -## Reserved identifiers the expansions introduce - -Every macro that turns a trait or impl "inside out" invents identifiers, and it wraps them in double underscores so they cannot collide with a user's names. Recognizing them is what lets you read raw expansions and the errors that quote them. The context type parameter is `__Context__` (overridable), the provider parameter is `__Provider__`, a rewritten `self` receiver is `__context__`, the component key generic is `__Component__`, an inner delegation table is `__Components__`, a looked-up delegate is `__Delegate__`, the `IsProviderFor` params tuple is `__Params__`, a getter's free field tag is `__Tag__`, and a namespace's table generic is `__Table__`. Throughout this file the readable names `Context`/`Provider`/`Params` stand in for these where clarity is served, but the emitted code uses the reserved forms. - -Two more names appear in expansions and resolve through the prelude: any CGP construct the output references is emitted as `::cgp::macro_prelude::`, so an expansion compiles in a crate that has only `cgp` in scope. You never write that path yourself. - ---- - -# Component-definition macros - -## `#[cgp_component]` - -The argument is either a bare provider name or a comma-separated set of keyed values: - -```ebnf -CgpComponentArgs -> ProviderName - | KeyValueArg ( `,` KeyValueArg )* `,`? - -ProviderName -> IDENTIFIER - -KeyValueArg -> `name` `:` ComponentName - | `provider` `:` IDENTIFIER - | `context` `:` IDENTIFIER - -ComponentName -> IDENTIFIER GenericArgs? -``` - -`ProviderName` is shorthand for setting `provider` alone. In the keyed form — written with brace delimiters, `#[cgp_component { … }]` — each of the three keys appears at most once, in any order, and `provider` is required. The `name` key defaults to the provider name with a `Component` suffix (`AreaCalculator` → `AreaCalculatorComponent`), and `context` defaults to `__Context__`. The provider name may not carry generics; the component name may. - -**Expansion invariant.** From one trait, `#[cgp_component]` emits five items plus standard provider impls, and this shape never varies: the **consumer trait** unchanged; the **provider trait** with `Self` moved to a leading `Context` parameter, every `self`/`Self` rewritten to `context`/`Context`, and an `IsProviderFor<{Name}Component, Context, Params>` supertrait; the **consumer blanket impl** (`Context: {Provider}` ⟹ consumer trait); the **provider blanket impl** (delegates through `DelegateComponent<{Name}Component>`); and the zero-sized **`{Name}Component` marker**. Alongside these it emits a `UseContext` impl, a `RedirectLookup` impl (what the `open` statement and namespaces dispatch through), one `UseDelegate` impl per `#[derive_delegate]`, and prefix impls per `#[prefix]`. Any trait generics land *after* the context in the provider trait and are grouped into the `IsProviderFor` `Params` tuple. See [components](components.md) for the full worked expansion. - -## `#[cgp_type]` and `#[cgp_getter]` - -Both reuse `CgpComponentArgs` verbatim and differ only in the default when `provider` is omitted: - -```ebnf -CgpTypeArgs -> CgpComponentArgs // default provider: {AssocType}TypeProvider -CgpGetterArgs -> CgpComponentArgs // default provider: strip leading `Has`, append `Getter` -``` - -`#[cgp_type]` keys its default off the *associated type's* name (`type Scalar;` → `ScalarTypeProvider`, marker `ScalarTypeProviderComponent`); `#[cgp_getter]` strips a leading `Has` from the trait name and appends `Getter` (`HasName` → `NameGetter`). Beyond the shared component output, `#[cgp_type]` adds a `UseType` blanket impl (the associated type *is* `T`) and a `WithProvider` impl adapting the built-in `TypeProvider`; `#[cgp_getter]` adds a `UseFields` impl (always) plus a `UseField` impl and a `WithProvider` impl (only when the trait has exactly one method). See [abstract-types](abstract-types.md) and [functions-and-getters](functions-and-getters.md). - ---- - -# Provider-writing macros - -## `#[cgp_impl]` - -The argument names the provider, optionally preceded by `new` and followed by a component override: - -```ebnf -CgpImplArgs -> `new`? ProviderType ( `:` ComponentType )? - -ProviderType -> Type -ComponentType -> Type -``` - -`new` makes the macro also emit `pub struct ;`. `ProviderType` takes the `Self` position of the generated provider impl — a plain name, a generic provider (`ScaledArea`), or the literal `Self`. The optional `: ComponentType` overrides the component in the generated `IsProviderFor` impl, defaulting to the provider trait name plus `Component`. - -Two forms of the impl header matter. **Omit `for Context`** and the macro inserts `__Context__` for you — this is the preferred form, and it reads like an ordinary trait impl. Write `impl Trait for Context` explicitly only to bound or name the context readably (a lifetime or HRTB the sugar cannot spell). Naming the provider **`Self`** — `#[cgp_impl(Self)]` — is a passthrough: it emits the block unchanged as a direct consumer-trait impl on the concrete context (the `for Context` clause is then *required*), which is how you apply companion attributes like `#[use_provider]` to a hand-written impl; `new` and the component override are ignored in this form. - -**Expansion invariant.** `#[cgp_impl]` desugars to `#[cgp_provider]` (or `#[cgp_new_provider]` with `new`): it moves the context back to the provider trait's leading position, swaps the provider name into `Self`, rewrites `&self` to `__context__: &__Context__`, every `self` to `__context__`, and every `Self` to the context type. The rewrite is scoped to the method bodies — a nested item's own `self`/`Self` is left alone (except inside a `macro!(…)` invocation, which the token rewrite cannot scope and so rewrites anyway). See [components](components.md). - -## `#[cgp_provider]` and `#[cgp_new_provider]` - -Both take a single optional component type; `#[cgp_new_provider]` additionally declares the provider struct (implied by the name, not the argument): - -```ebnf -CgpProviderArgs -> ComponentType? -CgpNewProviderArgs -> ComponentType? - -ComponentType -> Type -``` - -Applied to a provider-trait impl written directly on a provider struct, the macro passes the impl through unchanged and derives the matching `IsProviderFor` impl from it — a copy with the body and associated types stripped, the trait swapped to `IsProviderFor`, and the same `where` clause kept. The trait arguments become `IsProviderFor<{Component}, {Context}, ({trailing params})>`: component first (the argument overrides the default), context second (the provider trait's leading argument), and a tuple of every remaining provider-trait parameter third (`()` when none). A **const argument in the provider trait's own argument list is rejected** with a spanned error, because a const cannot key the type-based `Params` tuple; a const generic on the provider *struct* flows through untouched. - ---- - -# Function, getter, and handler macros - -These macros are applied to a plain Rust function or trait, so most take only an optional name and read the rest of their behavior off the item's shape. Their grammars are small: - -```ebnf -CgpFnArgs -> TraitName? // #[cgp_fn]: default = fn name in PascalCase -CgpComputerArgs -> ProviderName? // #[cgp_computer]: default = fn name in PascalCase -CgpProducerArgs -> ProviderName? // #[cgp_producer]: default = fn name in PascalCase -BlanketTraitArgs -> ContextName? // #[blanket_trait]: default = __Context__ - -TraitName -> IDENTIFIER -ProviderName -> IDENTIFIER -ContextName -> IDENTIFIER -``` - -`#[cgp_auto_getter]`, `#[cgp_auto_dispatch]`, and `#[async_trait]` take **no argument** — they are plain attributes on a trait, with no custom grammar. Their behavior comes entirely from the annotated trait and from separate companion attributes. - -The rest of each function macro's contract is read from the item, not the argument. `#[cgp_fn]` lifts function generics onto both the generated trait and impl while keeping the `where` clause impl-only, turns `#[implicit]` parameters into `HasField` reads, and forbids generics on the desugared *method* (a method-level generic is silently treated as a trait/impl generic). `#[cgp_computer]` collects the function's parameters into one tuple `Input`, chooses the base component and promotion bundle from whether the function is `async` and whether it returns `Result`, and wires the rest of the handler family by promotion; `#[cgp_producer]` requires a zero-parameter, non-`async`, non-generic function. `#[async_trait]` rewrites each `async fn` to `-> impl Future`. See [functions-and-getters](functions-and-getters.md) and [handlers](handlers.md) for the expansions. - ---- - -# Attribute modifiers - -These attributes refine what a host macro generates; none is a standalone macro. The table names each one's form, what it contributes, and which host macros accept it. - -- **`#[implicit]`** — a bare marker on a typed, plain-identifier function argument (`#[implicit] width: f64`); no argument of its own, and `#[implicit(x)]` or `#[implicit = "x"]` is rejected. Removes the argument, adds a `HasField` bound, and binds the value at the top of the body. A *mutable* implicit (its type carries a `&mut`) must be the sole implicit and needs `&mut self`. Host: `#[cgp_fn]`, `#[cgp_impl]`. -- **`#[uses(Trait, Trait

, …)]`** — a comma-separated list of trait bounds, accumulating across repeats. Adds each as a `Self:` impl-side bound. The simple `Trait` form reads like an import and is idiomatic, but any `where`-clause bound is accepted, including associated-type equality (`HasErrorType`); prefer `#[use_type]`'s equality form to pin an abstract type. Host: `#[cgp_fn]`, `#[cgp_impl]`. -- **`#[extend(Trait, Trait

, …)]`** — same simple-path list. Adds each as a *supertrait* of the generated trait (and, in `#[cgp_fn]`, also as an impl bound). Host: `#[cgp_fn]`, `#[cgp_component]`. Not `#[cgp_impl]` (a provider impl has no trait to attach supertraits to). -- **`#[extend_where(Pred, …)]`** — a list of full `where`-clause predicates, including associated-type equality, added verbatim to the generated trait's `where` clause. Host: `#[cgp_fn]` only. -- **`#[impl_generics(Param: Bound, …)]`** — bounded generic parameters added to the generated impl **only**, never the trait. Host: `#[cgp_fn]` only. -- **`#[use_provider(Provider: Trait<…> + …, …)]`** — completes an inner provider bound by inserting the context as the trait's leading argument (you write `: AreaCalculator`, it means `AreaCalculator`) and moving it into the `where` clause. Several bounds via commas or stacked attributes. It only *completes the bound* — it never rewrites the body, which must still call `Provider::method(self)` explicitly. Host: `#[cgp_impl]`, `#[cgp_fn]`. -- **`#[derive_delegate(Wrapper)]`** — generates a `UseDelegate`-style dispatcher impl keyed on `Param`; repeat for several dispatchers; the key may be a tuple `Wrapper<(A, B)>`. **Legacy** — prefer the `open` statement; see [wiring](wiring.md). Host: `#[cgp_component]`. -- **`#[prefix(@Path in Namespace)]`** — registers the component into `Namespace` under `@Path`, emitting one `RedirectLookup` impl; repeat for several namespaces. Host: `#[cgp_component]`. -- **`#[default_impl(T in DefaultImpls1)]`** — registers a `#[cgp_impl]` provider as a namespace's per-type default for `T`. Host: `#[cgp_impl]`. - -The check macros carry their own modifiers, covered with them below: `#[check_trait(Name)]` and `#[check_providers(…)]` on a table, `#[check_params(…)]` and `#[skip_check]` on an entry. - -## `#[use_type]` - -This attribute has enough structure to warrant a grammar of its own: - -```ebnf -UseTypeArgs -> UseTypeSpec ( `,` UseTypeSpec )* `,`? - -UseTypeSpec -> TraitPath `.` TypeItems ( `in` ContextPath )? - -ContextPath -> TypePath -TraitPath -> TypePath - -TypeItems -> UseTypeIdent - | `{` UseTypeIdent ( `,` UseTypeIdent )* `,`? `}` - -UseTypeIdent -> IDENTIFIER ( `as` IDENTIFIER )? ( `=` Type )? -``` - -The `.` (not `::`) after the trait path starts the associated-type list — a trait path keeps its own `::` segments and may carry generics. Omitting the `in ContextPath` suffix defaults the rewrite target to `Self`; an `in Types` suffix rewrites against a named generic parameter and adds `Types: Trait` as a `where` bound rather than a supertrait (`in` is a reserved keyword, so it can never be mistaken for part of a type, and reads like the `in` in `#[prefix(@Path in Namespace)]`). In each `UseTypeIdent`, `as` gives a local alias to write in signatures, and `= Type` pins the type with an equality bound (accepted on `#[cgp_fn]`/`#[cgp_impl]`, **rejected on `#[cgp_component]`**, whose trait definition cannot carry the impl-side equality). The macro rewrites every bare mention of the imported identifier (or alias) to the fully-qualified `::Type`. Host: `#[cgp_fn]`, `#[cgp_impl]`, `#[cgp_component]`. See [abstract-types](abstract-types.md). - ---- - -# Wiring and checking macros - -## `delegate_components!` - -The body is an optional generic list and `new` keyword, a target type, and a brace-delimited table: - -```ebnf -DelegateComponents -> Generics? `new`? TargetType `{` TableBody `}` - -TargetType -> Type - -TableBody -> Statement* ( Mapping ( `,` Mapping )* `,`? )? - -Statement -> OpenStmt | NamespaceStmt | ForStmt - -OpenStmt -> `open` ( `{` Type ( `,` Type )* `,`? `}` | Type ) `;` - -Mapping -> Key `:` ProviderValue - | Key `->` ProviderValue - | Key `=>` Path - -Key -> SingleKey | MultiKey | PathKey -SingleKey -> Generics? Type -MultiKey -> `[` SingleKey ( `,` SingleKey )* `,`? `]` -PathKey -> Generics? Path - -ProviderValue -> Type - | IDENTIFIER `<` `new` TargetType `{` TableBody `}` `>` - -Path -> `@` PathSegment ( `.` PathSegment )* -PathSegment -> Type -``` - -Every **statement must lead the block, before any mapping** — the parser reads statements first, so an `open`, `namespace`, or `for` written after a `Key: Value` line fails to parse (see error decoding below). A leading `Generics` list makes the whole table generic over the target; `new` also emits the target struct. Of the three mapping operators, `` `:` `` (map to a provider) is the common one, while `` `->` `` (delegate to the value's own entry) and `` `=>` `` (redirect along an `@`-path) belong to the namespace machinery. A `Key` may be one type, a bracketed list expanding to one entry each, or an `@`-path. The nested-table `ProviderValue` form (`UseDelegate`) defines an inner table in place — the legacy dispatch form. An `OpenStmt` opens one component (braces optional) or several (braces required) for per-value `@Component.Key: Provider` wiring folded into the context's own table. **No attributes are accepted** on the table or any entry; an unknown attribute is a spanned error, not silently dropped. See [wiring](wiring.md). - -**Expansion invariant.** Each plain `Key: Provider` entry emits a `DelegateComponent` impl with `Delegate = Provider` plus a forwarding `IsProviderFor` impl that threads the provider's dependencies back through the target. An `open Component;` header emits `DelegateComponent` = `RedirectLookup>`, and each `@Component.Value: Provider` entry stores that provider under the path key `PathCons>` on the target; the `RedirectLookup` impl appends the dispatch parameter onto the path at resolution time. - -## `delegate_and_check_components!` - -The same table shape, with a table-level check-trait attribute and per-entry check attributes: - -```ebnf -DelegateAndCheck -> TableAttr* Generics? `new`? TargetType `{` TableBody `}` - -TableAttr -> `#` `[` `check_trait` `(` IDENTIFIER `)` `]` - -TableBody -> Statement* ( CheckedMapping ( `,` CheckedMapping )* `,`? )? - -CheckedMapping -> EntryAttr? Mapping - -EntryAttr -> `#` `[` `check_params` `(` Type ( `,` Type )* `,`? `)` `]` - | `#` `[` `skip_check` `]` -``` - -`Mapping`, `Key`, `ProviderValue`, and `Statement` are exactly `delegate_components!`'s. This macro wires each entry and derives a check for it, so its check trait defaults to `__CanUse{Context}` (distinct from `check_components!`'s `__Check{Context}`, so both fit one module). Each mapping carries at most one `EntryAttr`; `#[check_params(…)]` supplies the concrete generic parameters a parameterized component's check needs, and `#[skip_check]` wires without checking — the two are mutually exclusive. See [checking](checking.md). - -## `check_components!` - -One or more check tables, each with optional attributes, generics, a context, an optional `where` clause, and a brace list of entries: - -```ebnf -CheckComponents -> CheckTable+ - -CheckTable -> TableAttr* Generics? ContextType WhereClause? `{` CheckEntries `}` - -TableAttr -> `#` `[` `check_trait` `(` IDENTIFIER `)` `]` - | `#` `[` `check_providers` `(` Type ( `,` Type )* `,`? `)` `]` - -ContextType -> Type - -CheckEntries -> ( CheckEntry ( `,` CheckEntry )* `,`? )? - -CheckEntry -> CheckKey ( `:` CheckValue )? - -CheckKey -> Type - | `[` Type ( `,` Type )* `,`? `]` - -CheckValue -> CheckParam - | `[` CheckParam ( `,` CheckParam )* `,`? `]` - -CheckParam -> Generics? Type -``` - -A `CheckEntry`'s value is omitted for a parameterless component and required otherwise; a bracketed key or value expands to the cartesian product, so a set of components is checked against a set of parameters. `#[check_trait(Name)]` overrides the derived `__Check{Context}` name. `#[check_providers(…)]` changes the check: instead of asserting `CanUseComponent` on the context, it asserts `IsProviderFor` on each listed provider, so each layer of a higher-order stack is verified on its own line. See [checking](checking.md). - -**Expansion invariant.** A check table emits one marker trait aliasing the asserted bound plus one empty impl per entry — the impl compiles only if the bound holds. The default form aliases `CanUseComponent` and implements it for the context; `#[check_providers(…)]` aliases `IsProviderFor` and implements it for each provider. A successful build *is* the passing assertion; the checks have no runtime existence. - -## `cgp_namespace!` - -The body is an optional generic list and `new`, a namespace name, an optional parent, and a table: - -```ebnf -CgpNamespace -> Generics? `new`? NamespaceName ( `:` ParentNamespace )? `{` NamespaceBody `}` - -NamespaceName -> IDENTIFIER GenericArgs? -ParentNamespace -> TypePath GenericArgs? - -NamespaceBody -> Statement* ( Mapping ( `,` Mapping )* `,`? )? -``` - -The mappings are `delegate_components!`'s `Mapping` — most often a `` `=>` `` redirect to an `@`-path or a `` `:` `` direct provider. The colon between `NamespaceName` and `ParentNamespace` is the *inheritance* colon, distinct from a mapping's `:`; naming a parent makes the namespace resolve everything the parent does plus its own entries. This macro also owns the two statement forms a context's `delegate_components!` table uses to consume a namespace: - -```ebnf -Statement -> NamespaceStmt | ForStmt - -NamespaceStmt -> `namespace` IDENTIFIER `;` - -ForStmt -> `for` `<` IDENTIFIER `,` IDENTIFIER `>` `in` TypePath WhereClause? - `{` ( NormalMapping ( `,` NormalMapping )* `,`? )? `}` - -NormalMapping -> Key `:` ProviderValue -``` - -A `NamespaceStmt` forwards every unresolved lookup through the named namespace. A `ForStmt` binds a key variable and a provider variable, reads each entry of the table named after `in`, and emits one `:` mapping per entry; its optional `where` clause is merged into every impl it generates. Like `delegate_components!`, the body accepts no attributes on any entry. See [namespaces](namespaces.md). - ---- - -# Type-level construction macros - -The four type-level macros build the vocabulary the rest of CGP keys on, and their grammars are minimal: - -```ebnf -SymbolInput -> STRING_LITERAL - -ProductInput -> ( Type ( `,` Type )* `,`? )? // Product! (type position) -ProductExpr -> ( Expression ( `,` Expression )* `,`? )? // product! (value position) - -SumInput -> ( Type ( `,` Type )* `,`? )? - -PathInput -> `@` PathSegment ( `.` PathSegment )* -PathSegment -> Type -``` - -Each expands to a fixed spine, and knowing the spine is what lets you read a printed type. `Symbol!("abc")` becomes `Symbol<3, Chars<'a', Chars<'b', Chars<'c', Nil>>>>` — the leading const is the *byte* length (so `Symbol!("世界")` records `6`), present only because stable Rust cannot compute a `Chars` length in const position. `Product![A, B]` becomes `Cons>` and `product![…]` builds the matching value; the empty forms are `Product![]`/`product![]` over `Nil`. `Sum![A, B]` becomes `Either>`, terminating in the uninhabited `Void` rather than `Nil`. `Path!(@app.error.FooComponent)` becomes a `PathCons` chain in which a lowercase, non-primitive segment is a `Symbol!` and a capitalized or primitive segment stays the named type. See [type-level-primitives](type-level-primitives.md). - ---- - -# Derives - -The data derives (`#[derive(HasField)]`, `HasFields`, `CgpData`, `CgpRecord`, `CgpVariant`, `BuildField`, `ExtractField`, `FromVariant`) take no custom arguments — they are ordinary derives whose behavior is fixed by the item they annotate. Their one grammar-shaped constraint is on **variant shape**: the enum derives (`CgpVariant`, `CgpData` on an enum, `ExtractField`, `FromVariant`) require every variant to be a single unnamed-field tuple variant (`Circle(Circle)`), because each variant's payload must be one nameable type. A fieldless (`Empty`), multi-field (`Pair(A, B)`), or struct-style (`Named { x: A }`) variant fails with **"Expected variant to contain exactly one unnamed field"** — wrap a richer payload in a dedicated struct. A named struct field is keyed by `Symbol!`, a tuple-struct field by `Index`. See [extensible-data](extensible-data.md). - ---- - -# Reading error messages - -CGP errors come from two layers — the macro's own parser, and the compiler resolving the expanded code — and each has a recognizable vocabulary. The parser errors are usually clear because most macros validate with spanned messages; the type errors are the ones that need decoding, because they name generated traits rather than the mistake you made. - -## Wiring and dependency errors - -**`the trait bound X: IsProviderFor is not satisfied`** is the most common, and it never means "write an `IsProviderFor` impl." Read it as *the provider `X` is not a valid provider for this component on `Ctx`, because one of its impl-side dependencies is unmet*. The truly missing bound is named nearby (a `HasField`, an abstract type, another capability); that is the thing to supply. `IsProviderFor` exists precisely to surface that named bound instead of a bare "trait not implemented" — see [components](components.md). - -**`the trait bound Ctx: DelegateComponent is not satisfied`** means the component was never wired on `Ctx` at all — add the `delegate_components!` entry. Contrast this with the `IsProviderFor` failure above, which means the component *was* wired but to a provider whose dependencies fail. A [check](checking.md) distinguishes the two for you: a failed `DelegateComponent` bound is "not wired," a failed `IsProviderFor` bound is "wired but unsatisfied." - -**A `CanUseComponent` error** comes from a `check_components!` or `delegate_and_check_components!` assertion firing at the wiring site — which is the point of checking. The named unmet bound is the first impl-side dependency the compiler could not satisfy; trace it through the provider chain to its root. Not every unmet bound is a CGP component: a plain trait or blanket-impl bound a provider also needs has no `…Component` marker and cannot be surfaced through wiring, so it must be satisfied by ordinary Rust means. - -**`overflow evaluating the requirement …`** on a component usually means a `UseContext` cycle — a component wired directly to `UseContext` whose only impl of that component *is* that delegation, so the context implements the consumer trait by calling a provider that implements the provider trait by calling the consumer trait. `UseContext` belongs as another provider's inner provider, not as a context's own delegate for the same component; see [wiring](wiring.md). - -## Decoding printed type-level values - -A long `Symbol>>` in an error is just a **field-name string** — read the `Chars` characters in order (here `name`) and ignore the leading length. A `Cons<…, Cons<…, Nil>>` is a record field list, an `Either<…, Either<…, Void>>` an enum variant list, and a `PathCons<…, …>` a namespace/redirect route. The [type-level-primitives](type-level-primitives.md) sub-skill is the full decoder ring; the shortcut is that any nested type ending in `Nil` is a product list and any ending in `Void` is a sum. - -## Errors that underline the whole macro block - -A coherence conflict (**`E0119`, conflicting implementations**) or an unconstrained-parameter error (**`E0207`**) whose caret covers an *entire* macro invocation rather than one entry is a span artifact, not a sign the whole block is wrong. The macros stamp generated items with a re-span onto the originating token, but a synthesized token that has lost its span falls back to the invocation site. When you see `E0119` between two generated impls, look for a **duplicated wiring entry** or two `open`/dispatch keys that overlap on the same type; when you see `E0207`, look for a generic parameter that appears in a provider or `#[cgp_fn]` header but is not bound by any argument or `where` clause. - -## Macro-parser errors - -These fire before expansion and are worth recognizing by shape. An **`expected `:`` near an `open`/`namespace`/`for`** almost always means a statement was written *after* a mapping — statements must lead the block. An **"unsupported attribute"** (or a spanned rejection) on a `delegate_components!` / `cgp_namespace!` entry means those macros accept no attributes there at all. A **const generic** on a `#[cgp_component]` trait or in a provider trait's argument list is rejected, because CGP dispatches on types, not values (an associated `const` item on the trait is fine). **"Expected variant to contain exactly one unnamed field"** is the variant-derive shape rule above. A **default-bodied `async fn`** inside `#[async_trait]` is unsupported (the body is not wrapped in `async {}`), and a **generic method on a `#[cgp_auto_dispatch]` trait** is rejected because Rust lacks the quantified bound the blanket impl would need. - ---- - -# Further reference - -This file is a companion to the per-topic sub-skills, which show the same expansions in worked context: [components](components.md), [wiring](wiring.md), [checking](checking.md), [functions-and-getters](functions-and-getters.md), [abstract-types](abstract-types.md), [higher-order-providers](higher-order-providers.md), [namespaces](namespaces.md), [handlers](handlers.md), [extensible-data](extensible-data.md), and [type-level-primitives](type-level-primitives.md). For the authoritative, exhaustive grammar and expansion of any single construct — every accepted form, every corner case, and the implementing source — fetch the online knowledge base at `https://github.com/contextgeneric/cgp/tree/main/docs`, whose `reference/macros/` documents carry the formal Syntax Grammar and Expansion sections these grammars are drawn from. diff --git a/docs/skills/cgp/references/modern-idioms.md b/docs/skills/cgp/references/modern-idioms.md deleted file mode 100644 index e18edb20..00000000 --- a/docs/skills/cgp/references/modern-idioms.md +++ /dev/null @@ -1,284 +0,0 @@ -# Modern idioms: reading and modernizing CGP - -The map from each older, explicit CGP form to the modern idiom you should prefer — so you can recognize legacy syntax in existing code, understand what it desugars to, and rewrite it in the vanilla-looking form. - -CGP's explicit forms came first, and they are still exactly what the macros desugar to, so you will keep meeting them in generated code, in reference Expansion sections, and in any codebase written before the newer idioms landed. The modern idioms exist to lower the barrier to entry: they let a provider look like an ordinary trait `impl`, a dependency look like a `use` import, and an abstract type look like a plain generic. **Prefer the modern idiom in all new code, and reach for an explicit form only when a construct genuinely cannot express the case** (the closing section lists those exceptions). This file is the bidirectional reference: read it forward to write modern CGP, and backward to decode legacy CGP you are asked to read or update. Assume `use cgp::prelude::*;`; the CGP version is v0.8.0. - -Each shift below shows the legacy form, the modern equivalent, and the mechanical rule that connects them. All of them desugar to the same generated code, so a rewrite is a readability change, never a behavioral one. - -## Provider shape: `#[cgp_impl]` over the raw provider forms - -Write a provider with [`#[cgp_impl]`](components.md), which keeps `self`, `Self`, and the consumer method signatures, rather than the lower-level [`#[cgp_provider]`/`#[cgp_new_provider]`](components.md), which require the inside-out provider-trait shape (the context moved to a leading type parameter, the receiver written as `context: &Context`). The legacy form: - -```rust -#[cgp_new_provider] -impl AreaCalculator for RectangleArea -where - Context: HasField, - Context: HasField, -{ - fn area(context: &Context) -> f64 { - *context.get_field(PhantomData::) - * *context.get_field(PhantomData::) - } -} -``` - -becomes, with `#[cgp_impl]` and [`#[implicit]`](functions-and-getters.md) arguments: - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} -``` - -`#[cgp_impl]` desugars back to `#[cgp_provider]`/`#[cgp_new_provider]`, so the raw forms remain what you read in expansions. Write the raw form yourself only when you need the inside-out shape directly — for instance a provider whose `Self` is a concrete context, or a bound the sugar cannot spell. - -## Context parameter: omit `for Context` - -Inside a `#[cgp_impl]` block, prefer the unqualified `impl AreaCalculator` and let the macro insert the reserved context parameter, rather than naming it as `impl AreaCalculator for Context`. Omitting `for Context` is what makes the provider read like an ordinary trait `impl`. Name the context explicitly only when you must bound it with a lifetime or higher-ranked bound the sugar cannot carry, or refer to it by a readable name. So the legacy - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator for Context -where - Context: HasDimensions, -{ - fn area(&self) -> f64 { self.width() * self.height() } -} -``` - -shortens to the bare header, with the bound moved to `#[uses]`: - -```rust -#[cgp_impl(new RectangleArea)] -#[uses(HasDimensions)] -impl AreaCalculator { - fn area(&self) -> f64 { self.width() * self.height() } -} -``` - -## Dependencies: `#[uses]` and `#[use_provider]` over hand-written `where` - -State a provider's [impl-side dependencies](components.md) with [`#[uses(...)]`](functions-and-getters.md) and [`#[use_provider(...)]`](higher-order-providers.md) rather than hand-written `where` clauses, so a dependency reads like a `use` import. `#[uses(CanCalculateArea)]` adds `Self: CanCalculateArea`; `#[use_provider(Inner: AreaCalculator)]` adds `Inner: AreaCalculator`, filling in the `` context argument a provider-trait bound needs. The legacy `where` forms: - -```rust -#[cgp_impl(new ScaledArea)] -impl AreaCalculator for Context -where - Self: HasField, - InnerCalculator: AreaCalculator, -{ - fn area(&self) -> f64 { /* ... */ } -} -``` - -become the attribute forms plus an implicit argument: - -```rust -#[cgp_impl(new ScaledArea)] -#[use_provider(InnerCalculator: AreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] scale_factor: f64) -> f64 { /* ... */ } -} -``` - -`#[uses]` accepts any bound a `where` clause allows, including one carrying associated-type equality, though the simple `Trait` form is idiomatic. Where that bound pins an **abstract type** — `Self: HasErrorType` — express it with the [`#[use_type]` equality form](#abstract-types-use_type-over-supertrait--selftype) below, rather than spelling the equality in `#[uses]` or writing a hand-written `where` clause; only equality on a trait you would never import from with `#[use_type]` (`Iterator`) stays an explicit `where` clause. When a provider imports several capabilities or binds several inner providers, combine them into one attribute separated by commas — `#[uses(CanQueryUserBalance, CanRaiseHttpError)]`, `#[use_provider(A: TraitA, B: TraitB)]` — rather than stacking the same attribute repeatedly; one combined attribute reads as a single dependency list. Both attributes also accept being split across repeated attributes, but do that only when a real reason calls for it. - -## Field reads: `#[implicit]` over a getter trait - -Read a value from a context field with an [`#[implicit]`](functions-and-getters.md) argument — in a `#[cgp_impl]` method exactly as in `#[cgp_fn]` — rather than declaring a getter trait and importing it. This is the default for *any* field a provider reads from its own context, since an implicit argument reads from `self` and takes a plain `&T` by reference without cloning — even a field several providers each read is written as the same implicit argument in each, not promoted to a getter. An implicit argument names both a local and the field it comes from, keeping the `HasField` machinery out of sight. The getter-trait version: - -```rust -#[cgp_auto_getter] -pub trait HasDimensions { - fn width(&self) -> &f64; - fn height(&self) -> &f64; -} - -#[cgp_impl(new RectangleArea)] -#[uses(HasDimensions)] -impl AreaCalculator { - fn area(&self) -> f64 { self.width() * self.height() } -} -``` - -collapses to reading the fields directly: - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} -``` - -Use `#[cgp_auto_getter]` sparingly — only where an implicit argument cannot reach. There are three such cases. The field lives on a type *other* than the provider's own context, so the getter is required as a `where` bound on that type (`Request: HasBasicAuthHeader`) and there is no `self` field for an implicit argument to read; the accessor must exist as a *named* capability other code depends on through `#[uses(HasName)]` or a supertrait; or the getter carries an *associated type inferred from the field* so the type stays abstract for callers. Everywhere else — including a same-context field read shared by several providers — prefer the implicit argument. Avoid `#[cgp_getter]` in ordinary code, since its full wireable component is for the advanced case of choosing the source field per context at wiring time. - -## Abstract types: `#[use_type]` over supertrait + `Self::Type` - -Bring an abstract type into a definition with [`#[use_type]`](abstract-types.md) and write it as a bare alias, rather than declaring the owning trait as a supertrait and qualifying every use as `Self::Type`. The attribute does both jobs: it adds the supertrait (on `#[cgp_component]`) or `where` bound (on `#[cgp_impl]`/`#[cgp_fn]`) *and* rewrites each bare `Error`/`Scalar` to its fully-qualified path. This is preferred even for the built-in error type — the legacy - -```rust -#[cgp_component(Loader)] -pub trait CanLoad: HasErrorType { - fn load(&self, path: &str) -> Result; -} -``` - -becomes - -```rust -#[cgp_component(Loader)] -#[use_type(HasErrorType.Error)] -pub trait CanLoad { - fn load(&self, path: &str) -> Result; -} -``` - -One rule bounds the rewrite: it fires only on the bare identifier of an *imported* type. A construct's own **local associated type stays qualified as `Self::Assoc`** — a handler that declares `type Output` writes `Self::Output`, never a bare `Output`. So a mixed signature like `Result` is exactly right: the local `Self::Output` stays qualified, the imported `Error` is bare. - -When a definition imports types from several traits, combine them into one `#[use_type]` attribute with the trait paths comma-separated — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — rather than stacking one attribute per trait; the combined form reads as a single import list. Several types from one trait use the braced list (`#[use_type(HasFooType.{Foo, Bar})]`). Stacking repeated `#[use_type]` attributes behaves identically, but reach for it only when a real reason calls for it. - -Prefer `#[use_type]` even when the type lives on a **foreign** type rather than `Self` — a type named by a generic parameter — using the trailing `in Context` clause. It rewrites the bare alias to `::Assoc` *and* supplies `Context: Trait`, so the hand-written - -```rust -#[cgp_auto_getter] -pub trait HasLoggedInUser -where - App: HasUserIdType, -{ - fn logged_in_user(&self) -> &Option; -} -``` - -becomes - -```rust -#[cgp_auto_getter] -#[use_type(HasUserIdType.UserId in App)] -pub trait HasLoggedInUser { - fn logged_in_user(&self) -> &Option; -} -``` - -with `App: HasUserIdType` supplied for you, so the plain `` parameter and the bare `UserId` are enough. - -Import a type with `#[use_type]` even when it *already* arrives transitively — as the supertrait of a trait pulled in by `#[uses]`. When `CanCreateFoo` has `HasFooType` as a supertrait, prefer re-importing over leaning on the transitive `Self::Foo`: - -```rust -#[cgp_fn] -#[uses(CanCreateFoo)] -#[use_type(HasFooType.Foo)] // re-import: bare `Foo`, explicit `HasFooType` dep -fn bar(&self) -> Foo { self.create_foo(42) } -``` - -instead of - -```rust -#[cgp_fn] -#[uses(CanCreateFoo)] -fn bar(&self) -> Self::Foo { self.create_foo(42) } // relies on the reachable supertrait -``` - -The re-import re-adds a harmless (already-implied) `Self: HasFooType` and rewrites the bare `Foo`, so `#[uses]` states the *capability* dependency and `#[use_type]` states the *type* dependency, both visible rather than one riding in silently. - -`#[use_type]` also *pins* an abstract type with the equality form `{Assoc = Type}`, which is the modern replacement for a hand-written `where Self: HasXType` clause — reach for it whenever a provider constrains an abstract type to a concrete one, rather than leaving the equality as an explicit `where`. On a `#[cgp_impl]`, the legacy - -```rust -#[cgp_impl(new DisplayHttpError)] -impl HttpErrorRaiser -where - Self: HasErrorType, - Code: IsStatusCode, - Detail: Display, -{ - fn raise_http_error(_code: Code, detail: Detail) -> AppError { /* ... */ } -} -``` - -becomes, with the equality moved into the attribute: - -```rust -#[cgp_impl(new DisplayHttpError)] -#[use_type(HasErrorType.{Error = AppError})] -impl HttpErrorRaiser -where - Code: IsStatusCode, - Detail: Display, -{ - fn raise_http_error(_code: Code, detail: Detail) -> AppError { /* ... */ } -} -``` - -The attribute emits the same `Self: HasErrorType` bound (and would rewrite any bare `Error`, though here the body names the concrete `AppError` directly). The right-hand side of `=` may even name *another* imported alias, which unifies two abstract types: `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`. Because the equality form produces an impl-side bound, it belongs on `#[cgp_impl]` and `#[cgp_fn]` only — it is rejected on `#[cgp_component]`. The one equality bound that *stays* a hand-written `where` clause is one on a trait you would never `#[use_type]` from, such as `Iterator`. - -## Supertraits: `#[extend]` over native `:` syntax - -Add a non-type capability supertrait to a `#[cgp_component]` trait with [`#[extend(...)]`](functions-and-getters.md) rather than native `pub trait CanDoX: Supertrait` syntax. Both produce the same trait, but `#[extend]` reads as importing a capability the trait re-exports, which is what a CGP supertrait actually is — a declared dependency, not a base class. It pairs symmetrically with `#[uses]`: `#[uses]` imports a capability for private use, `#[extend]` re-exports one as part of the trait's contract. The native - -```rust -#[cgp_component(Greeter)] -pub trait CanGreet: HasName { - fn greet(&self) -> String; -} -``` - -becomes - -```rust -#[cgp_component(Greeter)] -#[extend(HasName)] -pub trait CanGreet { - fn greet(&self) -> String; -} -``` - -Use `#[extend]` for a supertrait that contributes only a *capability* (like `HasName`, read through the getter); use `#[use_type]` instead when the supertrait is an abstract-type component whose associated type the signature names, since `#[use_type]` also rewrites the type. In `#[cgp_fn]`, whose `where` clauses are impl-side dependencies, `#[extend]` is the only way to declare a supertrait at all. - -## Per-type dispatch: `open` and namespaces over `UseDelegate` - -Route a generic-parameter component to a different provider per type with the [`open` statement](wiring.md) or a [namespace](namespaces.md), rather than the legacy [`UseDelegate`](higher-order-providers.md) nested-table pattern. Both ride the `RedirectLookup` impl every `#[cgp_component]` already generates, so they store the per-type entries directly on the context with no wrapper type. The legacy nested table: - -```rust -delegate_components! { - MyApp { - AreaCalculatorComponent: - UseDelegate, - } -} -``` - -becomes the inline `open` form: - -```rust -delegate_components! { - MyApp { - open AreaCalculatorComponent; - - @AreaCalculatorComponent.Rectangle: RectangleArea, - @AreaCalculatorComponent.Circle: CircleArea, - } -} -``` - -Because `open` and namespaces ride `RedirectLookup`, a **new** component you dispatch this way needs no [`#[derive_delegate(UseDelegate)]`](macro-grammar.md) attribute — that attribute exists only to generate the `UseDelegate` provider the nested-table form relies on. You will still see `#[derive_delegate]` on some CGP-shipped components (the error and handler families) so their existing `UseDelegate` wiring keeps working, but code dispatching only through `open` or a namespace can omit it. Prefer `open` for a context wiring its own components, and a namespace when a reusable, inheritable dispatch table is worth sharing. - -## When the explicit forms are still right - -A handful of cases genuinely need an explicit form, and choosing one there is not a regression. Keep an explicit `where` clause for an associated-type-equality bound on a non-`#[use_type]` trait — `Iterator`, `From`, and the like — which reads more clearly as a `where` clause than in an import-shaped `#[uses]` (which does accept it); but note the exception's own exception: an equality bound on an **abstract-type** trait (`Self: HasErrorType`) is *not* one of these, because the [`#[use_type]` equality form](#abstract-types-use_type-over-supertrait--selftype) `#[use_type(HasErrorType.{Error = AppError})]` does express it and is preferred; leave only equality on a non-`#[use_type]` trait as a hand-written `where`. Name the context explicitly, `impl Trait for Context`, to attach a lifetime or higher-ranked bound the sugar cannot carry, or when `Self` must be a concrete context (the `#[cgp_impl(Self)]` passthrough is the direct-impl case). Reach for `#[cgp_getter]` when you specifically want to choose which field a getter reads per context at wiring time. Write a raw provider-trait `impl` when you need the inside-out shape directly. And keep a local associated type qualified as `Self::Output` always — it is never a `#[use_type]` import. - -## Reading pre-0.7 code: renamed and removed names - -Very old code and pre-0.7 write-ups can use names that no longer exist, as opposed to the still-valid legacy *forms* above. These do not compile against current CGP, so treat them as signals to translate, not to copy: the attribute `#[cgp_context]` was removed (a context is now assembled with `delegate_components!` and the derive/getter machinery rather than a dedicated context macro), and the abstract-type provider trait once called `ProvideType` is now `TypeProvider`. When you meet a name the current prelude does not export, assume it was renamed or removed rather than that you are misremembering, and fetch the online knowledge base's changelog and reference to find its modern spelling; do not reintroduce a removed name into new code. - -## Related sub-skills - -Every shift here is a shortcut over a construct documented in full elsewhere: provider forms in [components](components.md), field injection and `#[uses]`/`#[extend]` in [functions-and-getters](functions-and-getters.md), abstract types and `#[use_type]` in [abstract-types](abstract-types.md), the inner-provider pattern in [higher-order-providers](higher-order-providers.md), per-type dispatch in [wiring](wiring.md) and [namespaces](namespaces.md), and the grammar and expansion of every form in [macro-grammar](macro-grammar.md). For how much CGP a problem needs in the first place, see [modularity-hierarchy](modularity-hierarchy.md). diff --git a/docs/skills/cgp/references/modularity-hierarchy.md b/docs/skills/cgp/references/modularity-hierarchy.md deleted file mode 100644 index 04208077..00000000 --- a/docs/skills/cgp/references/modularity-hierarchy.md +++ /dev/null @@ -1,162 +0,0 @@ -# Modularity hierarchy - -A spectrum of how decoupled an implementation can be from the type it serves, from plain generic functions up to per-provider wiring, so you can pick how much CGP machinery a problem actually needs. - -CGP is not all-or-nothing. The same capability — here, serializing a value with Serde — can be expressed at several levels of modularity, each more decoupled than the last and each carrying more machinery in exchange. This page walks the spectrum on one running example so a reader can stop at the first level that solves the problem rather than reaching for the heaviest tool by reflex. Assume `use cgp::prelude::*;` throughout; the CGP version is v0.8.0. - -## The coherence problem the hierarchy escapes - -What forces this hierarchy to exist is Rust's coherence rules, which guarantee that every trait lookup resolves to one globally unique implementation. Two rules enforce that uniqueness. The **overlap rule** forbids two implementations that could both apply to the same type — you cannot blanket-implement `Serialize` for every `T: Display` *and* for every `T: AsRef<[u8]>`, because a `String` satisfies both and the compiler has no principled way to choose. The **orphan rule** forbids implementing a trait for a type unless your crate owns either the trait or the type — you cannot implement someone else's `Serialize` for someone else's `Vec`. Each level below loosens one more of these constraints. CGP's escape route is to move the type that coherence ranges over — the `Self` of the implementation — into a position the implementing crate always owns, then restore a single unambiguous answer locally, one [context](components.md) at a time, through [wiring](wiring.md). See [coherence](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/coherence.md) for the full framing. - -## Level 1 — one implementation per interface - -The least machinery is a generic function or a blanket trait impl, which both define exactly one implementation behind an interface. A generic function captures the logic and its bounds in one place: - -```rust -pub fn serialize_bytes, S: Serializer>( - value: &Value, - serializer: S, -) -> Result { ... } -``` - -A blanket trait carries the same one-implementation limitation but reads more ergonomically at the call site, since the bound hides behind the trait impl and the caller writes a method: - -```rust -pub trait CanSerializeBytes { - fn serialize_bytes(&self, serializer: S) -> Result; -} - -impl> CanSerializeBytes for Value { - fn serialize_bytes(&self, serializer: S) -> Result { ... } -} -``` - -The gain is reuse with zero ceremony. The limitation is absolute: there can be exactly one blanket impl, so you cannot offer two ways to serialize bytes and let a caller pick between them. - -## Level 2 — one unique implementation per type per interface - -A vanilla Rust trait lifts the one-implementation limit slightly: many types may share the interface, but coherence still permits at most one implementation per type. Each type that wants the behavior writes its own impl: - -```rust -pub trait Serialize { - fn serialize(&self, serializer: S) -> Result; -} - -impl Serialize for Vec { - fn serialize(&self, serializer: S) -> Result { - self.serialize_bytes(serializer) - } -} - -impl<'a> Serialize for &'a [u8] { - fn serialize(&self, serializer: S) -> Result { - self.serialize_bytes(serializer) - } -} -``` - -The gain is that different types can be serialized differently. The cost is duplication: `Vec` and `&[u8]` each need an explicit impl even though the logic is identical. The body can still call out to a Level-1 building block such as `CanSerializeBytes` to share the actual work, so the duplication is confined to the boilerplate of forwarding. The remaining limitation is the one-impl-per-type ceiling — there is still no way to give `Vec` two serialization strategies and choose between them. - -## Level 3 — multiple implementations per type, globally unique wiring - -Applying basic CGP to a vanilla trait removes the duplication of Level 2 by turning the shared logic into a reusable [provider](components.md) and letting each type [wire](wiring.md) to it. The trait keeps its original shape; `#[cgp_component]` generates the [consumer trait](components.md) and [provider trait](components.md) pair, `#[cgp_impl(new ...)]` defines a named provider once, and `delegate_components!` points each type at it: - -```rust -#[cgp_component(ValueSerializer)] -pub trait Serialize { - fn serialize(&self, serializer: S) -> Result; -} - -#[cgp_impl(new SerializeBytes)] -impl> ValueSerializer for Value { - fn serialize(&self, serializer: S) -> Result { ... } -} - -delegate_components! { - Vec { - ValueSerializerComponent: SerializeBytes, - } -} - -delegate_components! { - <'a> &'a [u8] { - ValueSerializerComponent: SerializeBytes, - } -} -``` - -The gain is real reuse without modifying the interface: `Serialize` is unchanged, so a type can still implement it directly without opting into CGP at all, and existing users of the trait are unaffected. The `ValueSerializer` provider trait removes the need for ad-hoc interfaces like `CanSerializeBytes`, and `delegate_components!` removes the manual forwarding of Level 2. The limitation is that coherence still binds the wiring itself: each type carries one global wiring, so a `Vec` entry conflicts with any overlapping `Vec` entry, the choice cannot be overridden per context, and the orphan rule still means you can only wire `Vec` from a crate that owns either `Serialize` or `Vec`. - -## Level 4 — unique wiring per type, per context - -Adding an explicit context parameter fully decouples the implementation from the type, so each context wires its own choices and the orphan rule lifts entirely. The trait changes shape: the original `Self` becomes an explicit `Value` parameter, so the component now dispatches on which concrete value type it serializes. Each context then folds its per-type choices straight into its own table with the `open` statement of `delegate_components!`: - -```rust -#[cgp_component(ValueSerializer)] -pub trait CanSerializeValue { - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer; -} - -delegate_components! { - new MyAppA { - open ValueSerializerComponent; - - @ValueSerializerComponent.Vec: SerializeBytes, - @ValueSerializerComponent.Vec: SerializeIterator, - } -} - -delegate_components! { - new MyAppB { - open ValueSerializerComponent; - - @ValueSerializerComponent.Vec: SerializeHex, - @ValueSerializerComponent.Vec: SerializeIterator, - } -} -``` - -The `open ValueSerializerComponent;` header opens the component for per-value wiring, and each `@ValueSerializerComponent.Value: Provider` entry assigns a provider for one concrete value type. The gain is that `MyAppA` and `MyAppB` resolve `Vec` to different providers — bytes versus hex — with no conflict, because each choice is coherent only within its own context. The orphan rule no longer applies: a context can wire `Vec` even when its crate owns neither `CanSerializeValue` nor `Vec`, as long as it owns the context type, so you never commit to a global serialization for `Vec` up front. The costs are that the trait must be modified to add the context parameter, and that every value type a context touches must be wired explicitly, which grows tedious for a large type set. - -The `open` form rides the dispatch machinery that every `#[cgp_component]` already generates, so the trait needs no extra option. A legacy alternative writes the same dispatch with a `#[derive_delegate(UseDelegate)]` attribute on the trait and a `UseDelegate: SerializeBytes, ... }>` nested table in each context's wiring; it is retained for compatibility but `open` is preferred for new code, and the two forms appear side by side in [wiring](wiring.md). - -## Level 5 — explicit wiring per type, per provider - -The finest grain overrides wiring *inside* a provider rather than at the context, using a [higher-order provider](higher-order-providers.md) whose inner provider defaults to `UseContext`. The default routes nested lookups back through the context as usual, while an explicit inner provider overrides one branch locally without touching the context's table: - -```rust -pub struct SerializeIteratorWith(pub PhantomData); - -#[cgp_impl(SerializeIteratorWith)] -impl ValueSerializer -where - for<'a> &'a Value: IntoIterator, - Provider: for<'a> ValueSerializer::Item>, -{ - fn serialize(&self, value: &Value, serializer: S) -> Result - where - S: serde::Serializer, - { ... } -} - -delegate_components! { - new MyAppA { - open ValueSerializerComponent; - - @ValueSerializerComponent.Vec: SerializeBytes, - @ValueSerializerComponent.Vec>: SerializeIteratorWith, - @ValueSerializerComponent.Vec: SerializeIteratorWith, - @ValueSerializerComponent.[u8, u64]: UseSerde, - } -} -``` - -Here `Vec>` serializes its inner `Vec` as hex strings, while a bare `Vec` elsewhere in the same context still serializes as bytes — the inner provider is overridden for that one branch only. Where `SerializeIteratorWith` is left without an argument, as for `Vec`, the `UseContext` default takes over and the item lookup goes back through the context, so the `u64` items resolve to `UseSerde` from the table. The gain is per-provider control: a wiring decision can be pinned at the point of use instead of globally at the context level. The cost is the higher-order plumbing itself — the extra provider parameter, the explicit context argument in the inner bound, and the discipline of choosing when to override versus when to defer to the context. - -## Choosing a level - -Read the spectrum as a ladder and stop at the first rung that fits. Levels 1 and 2 are plain Rust and need no CGP at all — reach for them when one implementation, or one per type, is genuinely all you need. Level 3 buys reuse and swappable providers while leaving the trait and its existing users untouched, the right entry point for retrofitting CGP onto an established trait. Level 4 is the canonical CGP shape, paying a modified interface for full per-context freedom and escape from the orphan rule. Level 5 is a local refinement layered on top of Level 4, used only where a single nested branch must diverge from the context's global choice. Each step up trades ceremony for decoupling, so the discipline is to climb only as far as the problem demands. - -Further reference: [coherence](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/coherence.md) for the rules this hierarchy escapes, and [modular serialization](https://github.com/contextgeneric/cgp/blob/main/docs/examples/modular-serialization.md) for the full worked example. diff --git a/docs/skills/cgp/references/namespaces.md b/docs/skills/cgp/references/namespaces.md deleted file mode 100644 index 0852a4fe..00000000 --- a/docs/skills/cgp/references/namespaces.md +++ /dev/null @@ -1,167 +0,0 @@ -# Namespaces - -A namespace is a reusable, named lookup table of component wirings that a context inherits wholesale and then selectively overrides — CGP's preset mechanism, expressed entirely at the type level with no runtime cost. - -As the component count of an application grows, the [wiring](wiring.md) on each context grows with it: every context spells out its own `delegate_components!` table entry by entry, and two contexts that should share the same set of providers must repeat the whole block. A namespace lifts that block out of any single context, gives it a name, and lets other contexts say "use everything in this namespace" to pull in the entire group at once. This is exactly the preset pattern — a curated bundle of defaults you adopt and then customize — and CGP has no separate `cgp_preset!` construct because a preset *is* a namespace. This file covers `cgp_namespace!` (defining and inheriting a namespace), the `RedirectLookup` provider that makes the indirection work, the `Path!` macro that addresses entries, and the `DefaultNamespace` family of default-resolution traits. - -## What a namespace is - -A namespace is not a context — it is a trait, named after the namespace, that carries a single `Delegate` associated type and is implemented once per key. A context that joins a namespace forwards its lookups through that trait, so the namespace supplies defaults without ever being instantiated or holding any wiring at the context level. The defining behavior is inheritance with override: a context inherits the namespace's entries as defaults, and any entry it wires directly on itself wins, because a context's own [`DelegateComponent`](wiring.md) entry resolves before the namespace fallback is consulted. - -The forwarding is keyed by a *path* rather than a bare component name, and that is what makes inheritance and selective override possible. A path is a type-level list of symbols and component names; keying on it lets one namespace inherit from another, lets a parent's whole subtree be rerouted at once, and lets a child context shadow a single inherited entry without disturbing the rest. - -## Defining a namespace with `cgp_namespace!` - -`cgp_namespace!` defines a namespace from a body that resembles a `delegate_components!` table. The `new` keyword tells the macro to emit the namespace's marker struct and its lookup trait; the entries inside map keys to redirect paths or to providers: - -```rust -cgp_namespace! { - new DefaultShowComponents { - [String, u64]: ShowWithDisplay, - } -} -``` - -Two entry forms appear in the body and generate different table contents. A `:` entry maps a key straight to a provider, exactly as in `delegate_components!` — the `[String, u64]: ShowWithDisplay` line above resolves both keys to the `ShowWithDisplay` provider. A `=>` entry instead redirects a key along a path: `FooProviderComponent => @MyFooComponent` says "when this namespace is asked for `FooProviderComponent`, look up the path `@MyFooComponent` instead of naming a provider outright," leaving the actual provider to be decided wherever the path lands. - -A namespace inherits from a parent by naming it after a colon in the header. The child then resolves everything the parent does, plus its own entries: - -```rust -cgp_namespace! { - new ExtendedNamespace: DefaultNamespace { - @cgp.core.error => - @app, - } -} -``` - -`ExtendedNamespace` inherits every entry `DefaultNamespace` resolves and additionally reroutes the entire `@cgp.core.error` subtree to `@app` — a single path-rewriting entry redirects a whole prefix of the parent namespace at once, not just one component. - -### What the macro generates - -With `new` present, the macro emits a backing struct (named with an `__…Components` wrapper) and the lookup trait carrying the table's `__Table__` generic parameter and a `Delegate` associated type: - -```rust -pub struct __MyNamespaceComponents; - -pub trait MyNamespace<__Table__> { - type Delegate; -} -``` - -Each `=>` entry becomes an `impl` of that trait for the entry's key, whose `Delegate` is a `RedirectLookup` pointing the table at the entry's path; each `:` entry becomes an `impl` whose `Delegate` is the named provider directly. For `FooProviderComponent => @MyFooComponent` the macro emits: - -```rust -impl<__Table__> MyNamespace<__Table__> for FooProviderComponent { - type Delegate = RedirectLookup<__Table__, PathCons>; -} -``` - -When a parent is named, the macro prepends a blanket impl that forwards every key the parent resolves down to the child, so the child inherits the parent's full table; the child's own entries are emitted after it and take precedence where their keys are more specific. - -## Attaching components and joining namespaces - -A namespace is consumed from two sides: components register into it, and contexts join it. A [component](components.md) attaches to a namespace through the `#[prefix(@path in Namespace)]` attribute on its [`#[cgp_component]`](components.md) trait, which emits one extra impl registering the component into the named namespace under a path prefix. CGP's own [`HasErrorType`](components.md), for example, carries `#[prefix(@cgp.core.error in DefaultNamespace)]`, placing the standard error wiring into the built-in `DefaultNamespace` so any context joining that namespace inherits it. The generated impl routes the component's lookup under the prefix path: - -```rust -impl<__Components__> MyNamespace<__Components__> for BarProviderComponent { - type Delegate = RedirectLookup< - __Components__, - PathCons>, - >; -} -``` - -A context joins a namespace inside `delegate_components!` with a `namespace` header line, after which every lookup it cannot resolve directly forwards through the namespace. A direct entry on the same context shadows just that key, leaving the rest of the inherited wiring intact: - -```rust -delegate_components! { - AppA { - namespace DefaultNamespace; - - @test.ShowImplComponent.u64: - ShowWithDisplay, // overrides only the u64 entry - } -} -``` - -The `namespace DefaultNamespace;` line emits a blanket `DelegateComponent` impl on `AppA` that forwards every key through `DefaultNamespace`, paired with the matching `IsProviderFor` forwarding so dependency errors stay diagnosable through [checking](checking.md). The direct `@test.ShowImplComponent.u64` line resolves first, so it wins for `u64` only — the inherit-and-override pattern in action. The override works because `DefaultNamespace` only routes the *marker* `ShowImplComponent` to a path that the context itself fills; a context cannot instead override a path the joined namespace *itself* registers (through a `:` body entry or a `#[default_impl]`), because the direct entry and the namespace's blanket `DelegateComponent` impl would both cover that path and conflict (`E0119`). To leave a path overridable, route the marker through the namespace but terminate the redirect on the context. Joining through `delegate_and_check_components!` does the same and additionally checks the entries the block writes directly, but its derivation does not cover the components the namespace itself brings in — so verifying the full inherited wiring is a job for a standalone `check_components!` (see [checking](checking.md)). - -## Paths with `Path!` - -A path is the type-level address that namespace entries redirect along, and `Path!` is the macro that builds one from a readable dotted, `@`-prefixed form. Each segment narrows the lookup one step — through a namespace, through a prefix, down to a component key — and the leading `@` is the sigil marking the body as a path rather than a plain type: - -```rust -type ErrorRoute = Path!(@app.error.ErrorRaiserComponent); -// PathCons>> -``` - -The encoding of each segment is decided by its first character: a single lowercase identifier that is not a primitive type name (like `app` or `error`) becomes a [`Symbol`](abstract-types.md) type-level string, while every capitalized segment (like `ErrorRaiserComponent`) is kept as the named type — typically a component key or namespace marker. The macro folds the segments right-to-left onto `Nil`, wrapping each in a `PathCons`. You rarely call `Path!` directly; the same `@`-path syntax is embedded inside `cgp_namespace!` entries and `#[prefix(...)]` attributes, which is where paths are most often written. These [type-level primitives](type-level-primitives.md) carry no runtime value. - -## The `RedirectLookup` provider - -`RedirectLookup` is the [provider](components.md) — a zero-sized marker struct, no runtime value — that turns a path-addressed entry back into a concrete provider, and it is the mechanism every namespace runs on under the hood. The ordinary provider blanket impl looks a component up in the context's own table keyed by the component-name marker; `RedirectLookup` instead consults a table keyed by an arbitrary type-level path, then delegates to whatever provider that entry holds. This decouples *which key* a component is looked up under from *which table* answers it — the basis for organizing wiring into namespaces. - -`RedirectLookup` is never written by hand; it is emitted by the macros. Every `#[cgp_component]` generates a `RedirectLookup` impl of its provider trait, and the namespace `=>` entries and `#[prefix]` attributes generate entries whose `Delegate` is a `RedirectLookup`. The generated impl performs one `DelegateComponent` lookup keyed on the path rather than on the component name: - -```rust -impl<__Context__, __Components__, __Path__> Greeter<__Context__> - for RedirectLookup<__Components__, __Path__> -where - __Components__: DelegateComponent<__Path__>, - <__Components__ as DelegateComponent<__Path__>>::Delegate: Greeter<__Context__>, -{ - fn greet(__context__: &__Context__) -> String { - <__Components__ as DelegateComponent<__Path__>>::Delegate::greet(__context__) - } -} -``` - -So `RedirectLookup` implements the provider trait whenever the table maps the path to a delegate that itself implements that trait, and forwards the call to it. When the provider trait carries a generic type parameter, the impl additionally appends that parameter onto the path before the lookup, so the redirected key can encode the generic argument — this is how per-type dispatch through a namespace works. - -## Default resolution: the `DefaultNamespace` family - -The traits that back namespaces come in three arities, differing only in how many type parameters take part in the key. `DefaultNamespace` keys a default purely on the component name; `DefaultImpls1` keys it on the component name *and* one further type — the shape for a per-type default where the same component resolves differently for `String` than for `u64` — and `DefaultImpls2` does the same for a pair. Each carries a single `Delegate` associated type and no method or data; resolution is the projection of `Delegate` from the matching impl: - -```rust -pub trait DefaultNamespace { - type Delegate; -} - -pub trait DefaultImpls1 { - type Delegate; -} -``` - -`DefaultNamespace` is the built-in namespace that `#[prefix(... in DefaultNamespace)]` registers components into and that a context joins with `namespace DefaultNamespace;`. A per-type default is registered with the `#[default_impl(T in DefaultImpls1)]` attribute on a provider impl, which emits `impl DefaultImpls1 for T { type Delegate = Provider; }`. The registration impl drops the provider's own impl-side `where` clause (its `#[use_type]`/`#[uses]` bounds), so a provider that depends on abstract types registers cleanly. Where the attribute may be *written* follows the orphan rule: the crate must own the namespace trait or the key — so an unprefixed component's marker key can be registered into a foreign namespace downstream, but a `#[prefix]`-ed component's key is a foreign `PathCons` path, confining its default to the namespace's own crate. A context then pulls those defaults in with a `for … in` loop that projects the `Delegate` for each type: - -```rust -delegate_components! { - App { - namespace DefaultNamespace; - - for in DefaultImpls1 { - @test.ShowImplComponent.T: Provider, - } - - @test.ShowImplComponent.u64: - ShowWithDisplay, // overrides the inherited default for u64 - } -} -``` - -The `for in DefaultImpls1` loop wires each type `T` by reading `T: DefaultImpls1`, and the direct `u64` line shadows whatever the namespace would otherwise supply for that type. The loop target can equally be a whole namespace defined with `cgp_namespace!`, such as the `DefaultShowComponents` namespace shown earlier — `for in DefaultShowComponents { … }` wires its listed types through the same projection. An optional `where` clause after the loop target (`for in Table where T: Clone { … }`) adds its bounds to every impl the loop emits, narrowing which types it wires. - -## Defining a preset once, reusing it across contexts - -The payoff is that a bundle of wiring is defined once and reused everywhere. A library publishes a namespace of sensible defaults — possibly extended from a base namespace as `ExtendedNamespace: DefaultNamespace` does — and any number of applications join it, inherit the whole bundle, and override only the entries specific to their needs. Each context's table shrinks to a `namespace` header plus a short list of overrides, no matter how many components the namespace bundles. Because the inheritance, the overrides, and the redirections are all resolved through trait projection and type-level paths, the entire arrangement is resolved at compile time with no runtime cost — a preset is simply one more namespace in the chain. - -## Related constructs - -Namespaces build directly on [wiring](wiring.md): a namespace is a `DelegateComponent` table addressed by paths, and a context joins one inside `delegate_components!`. The keys a namespace maps are the `…Component` markers of [components](components.md), attached via the `#[prefix(...)]` attribute on a `#[cgp_component]` trait, and the `IsProviderFor` forwarding a `namespace` header emits feeds the completeness guarantees in [checking](checking.md). The `@`-paths and `Symbol` segments are [type-level primitives](type-level-primitives.md). The per-type dispatch `RedirectLookup` enables — appending a generic parameter onto the lookup path — is the same mechanism the type-parameter dispatch forms in [higher-order providers](higher-order-providers.md) use. - -## Further reference - -Online docs: [concepts/namespaces.md](https://github.com/contextgeneric/cgp/blob/main/docs/concepts/namespaces.md), [reference/macros/cgp_namespace.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/cgp_namespace.md), [reference/providers/redirect_lookup.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/redirect_lookup.md), [reference/macros/path.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/path.md). diff --git a/docs/skills/cgp/references/type-level-primitives.md b/docs/skills/cgp/references/type-level-primitives.md deleted file mode 100644 index f742494f..00000000 --- a/docs/skills/cgp/references/type-level-primitives.md +++ /dev/null @@ -1,197 +0,0 @@ -# Type-level primitives - -The handful of zero-sized types and type macros that CGP folds strings, numbers, lists, choices, and routes into the type system, so the compiler can match a field name or a wiring route through trait resolution alone. - -## The idea - -CGP keys nearly everything by *type*, not by value. A getter looks up a field by a tag type; a [wiring](wiring.md) table selects a [provider](components.md) by a [component](components.md) key type; a [namespace](namespaces.md) re-routes a lookup along a path type. For that to work, things that are normally values — a field name string, a tuple position, a list of fields, a lifetime — have to be encoded as types the compiler can compare and dispatch on. The primitives in this reference are those encodings. Each is a familiar value-level idea lifted into a type: a string becomes a type-level character list, a number becomes a const-generic marker, a list becomes a recursive cons cell, a sum becomes a recursive branch. - -These types are almost never written by hand. They are produced by macros (`Symbol!`, `Product!`, `Sum!`, `Path!`) or emitted by derives, and a reader mostly meets them when *decoding a type the compiler prints* — in an error message, a macro-expansion dump, or a hover. (`cargo cgp expand` resugars them back to `Symbol!`/`Product!`/`Path!`, so a raw spine there means the resugaring declined; an error message or a plain `cargo expand` shows them as they are.) This reference is a decoder ring: skim it to read off what a long nested type means. The prose below always uses the readable `Cons`/`Nil`/`Symbol!` forms, which are exactly what the compiler prints — no abbreviations or aliases are substituted for these types. - -Assume `use cgp::prelude::*;` throughout. - -## Type-level lists: `Product!`, `Cons`, `Nil` - -A type-level list is a compile-time linked list — the analogue of a tuple that generic code can take apart one element at a time. It is built from two cells: `Cons`, a pair holding the first element and the rest of the list, and `Nil`, a unit struct marking the end. Chained to the right and terminated by `Nil`, they form an *anonymous product type* — a record-shaped type whose width and contents a provider can walk without knowing the concrete struct it came from. - -```rust -pub struct Cons(pub Head, pub Tail); -pub struct Nil; -``` - -The `Product!` macro is the sugar a programmer writes instead of nesting `Cons` by hand, and the lowercase `product!` builds a matching value with the tuple-struct constructor: - -```rust -type Row = Product![u32, String, bool]; -// Row == Cons>> - -let row: Row = product![1, "hi".to_string(), true]; -// row == Cons(1, Cons("hi".to_string(), Cons(true, Nil))) -``` - -The list is what makes structural, field-by-field code possible. A struct's fields are exposed as one `Product!` type through `HasFields`, so a provider written once to recurse over `Cons`/`Nil` — a `Nil` impl for the base case, a `Cons` impl for the step — iterates, reads, or rebuilds *any* struct's fields. The elements are usually [`Field`](#field-a-named-value) entries pairing a name with a value, so a derived struct's layout reads as a `Product!` of `Field` cells. See [extensible data](extensible-data.md) for how that machinery is used. - -## Type-level sums: `Sum!`, `Either`, `Void` - -A type-level sum is the dual of the list: where a `Product!` holds a value for *every* element at once, a `Sum!` holds a value for exactly *one* branch — a tagged union the compiler can walk variant by variant. It shares the same right-nested shape but branches at each step instead of pairing, and terminates in an uninhabited marker instead of a constructible one. - -```rust -pub enum Either { Left(Head), Right(Tail) } -pub enum Void {} -``` - -`Either` is the sum cell — `Left(head)` selects this branch, `Right(tail)` defers to the rest — and `Void`, an empty enum with no values, closes the chain. The `Sum!` macro folds a list of types onto that spine, and a value picks one branch by how deep it sits: - -```rust -type Token = Sum![u32, String, bool]; -// Token == Either>> - -let t: Token = Either::Right(Either::Left("hi".to_string())); // the String branch -``` - -The terminator is the one real difference from the product spine, and it is load-bearing. A product ends in the constructible `Nil` because an empty record is a valid value; a sum ends in the *uninhabited* `Void` because an empty choice has no value to pick. After an extractor has tried every variant and matched none, the leftover has type `Void` — a value that cannot exist — which the machinery discharges with an empty `match self {}`, making a fully-handled variant match total at compile time with no unreachable runtime branch. An enum's variants are exposed as a `Sum!` of `Field` entries through `HasFields`, mirroring how a struct's fields are a `Product!`. - -## Type-level strings: `Symbol!`, `Symbol`, `Chars` - -A type-level string is a field name encoded as a type, so the name can drive trait resolution. CGP's getter, `HasField`, keys each field by a `Tag` type; to read a field called `name`, the string `"name"` must become a unique type, so that two symbols from the same string are the *same* type and two from different strings are *different* types. `Symbol!("name")` produces exactly that. - -The reason it is a *list of characters* rather than one const value is a stable-Rust limitation: a `&str` cannot be a const-generic parameter, but a single `char` can. So `Chars` spells the string out one character at a time — the specialized analogue of `Cons` where the head is a `const char` — and `Symbol` wraps that list together with the byte length: - -```rust -pub struct Chars(pub PhantomData); -pub struct Symbol(pub PhantomData); -``` - -The `Symbol!` macro hides all of this. The leading `LEN` const is the part most likely to puzzle a reader: stable Rust cannot compute a `Chars` chain's length inside a const-generic context, so the macro precomputes `str::len()` — the *byte* length — and bakes it into the type: - -```rust -// before -Symbol!("abc") -// after -Symbol<3, Chars<'a', Chars<'b', Chars<'c', Nil>>>> -``` - -Because `LEN` is the byte length, a multi-byte string records its UTF-8 width: `Symbol!("世界你好")` records `12`, while its character list still has one `Chars` node per scalar. The empty string is `Symbol<0, Nil>`. A type-level string is most often seen as the tag in a getter bound, where it names the field a provider reads without that context naming the provider: - -```rust -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasField, -{ - fn greet(&self) { - println!("Hello, {}!", self.get_field(PhantomData::)); - } -} -``` - -The same string can be recovered at runtime — see [`StaticFormat`](#staticformat-recovering-strings-and-paths) below. - -## `Index`: type-level numbers - -`Index` is the numeric counterpart to `Symbol!` — a `usize` lifted into a type, used to tag a tuple-struct field that has a position but no name. Where a named field is keyed by `Symbol!("name")`, the field at position `N` is keyed by `Index`, so `Index<0>`, `Index<1>`, and `Index<2>` are three distinct tag types standing in for `.0`, `.1`, and `.2`. - -```rust -pub struct Index; -``` - -It is a zero-sized marker: the number lives entirely in the type, so a tuple struct can carry a `HasField>` impl and a `HasField>` impl side by side and the compiler selects the right one purely from the tag. Selecting a wrong position — `Index<5>` on a three-field struct — is a type error, not a runtime panic, because no matching impl exists. `Index` prints its number directly through `Display`, so `Index::<2>.to_string()` is `"2"` and the tag is legible in diagnostics. - -## `Field`: a named value - -`Field` is the element type that fills both spines — a value paired with the type-level tag naming it. A bare `Product![String, u8]` records only types and order; wrapping each element as `Field` attaches the name as a phantom type, making the structural representation self-describing so a provider can match on the tag to find the field it wants. - -```rust -pub struct Field { - pub value: Value, - pub phantom: PhantomData, -} -``` - -The tag is a phantom — needed only at compile time for resolution — so a `Field` is exactly as large as its `Value` and costs nothing at runtime. It is built from a value with no tag argument, since the tag is fixed by the target type: `let f: Field = "Alice".to_string().into();`. The same shape names a record field (tag from `Symbol!` or `Index`) and an enum variant (tag from `Symbol!`, value being the payload), which is why a derived `HasFields` is a `Product!` or `Sum!` of `Field` entries: - -```rust -#[derive(HasFields)] -pub struct Person { pub name: String, pub age: u8 } - -// generated: -// type Fields = Product![ -// Field, -// Field, -// ]; -``` - -## `Path!` and `PathCons`: type-level routes - -A type-level path is a route through nested [wiring](wiring.md) tables, expressed as a single type. Where a bare component key picks one entry out of a context's table, a path points at an entry behind one or more layers of indirection — inside a [namespace](namespaces.md), under a prefix — by listing the segments to walk left to right. `PathCons` is the cons cell of that route, terminated by `Nil`, and it differs from the `Cons` product spine in one way: both `Head` and `Tail` are `?Sized`, because a path segment is a pure type-level marker that never needs a known size. - -```rust -pub struct PathCons(pub PhantomData, pub PhantomData); -``` - -The `Path!` macro builds the route from a dotted, `@`-prefixed name, encoding each segment by case: a lowercase, non-primitive identifier becomes a `Symbol!` type-level string, and a capitalized name stays the named type it spells (typically a component key or namespace marker): - -```rust -type ErrorRoute = Path!(@app.error.ErrorRaiserComponent); -// PathCons>> -``` - -A path names only *where to look*, never a provider directly, so the same path resolves to different providers depending on the table it is walked against — the job of the `RedirectLookup` provider that consumes it. This same `@`-path syntax appears verbatim inside namespace entries, which is where paths are most often written rather than through the bare macro. See [namespaces](namespaces.md) for the redirected-lookup mechanism. - -## `Life<'a>`: a lifetime as a type - -`Life<'a>` lifts a lifetime into a type, so a CGP trait that borrows can still ride through wiring machinery that only accepts types. CGP's dependency marker, `IsProviderFor`, takes a tuple of a component's generic parameters as one type argument — and a tuple member must be a type, never a bare lifetime. A consumer trait declaring `fn get_reference(&self) -> &'a T` therefore cannot record its `'a` directly; `Life<'a>` is the conversion that packages the lifetime as a type so it can sit in the tuple as `(Life<'a>, T)`. - -```rust -pub struct Life<'a>(pub PhantomData<*mut &'a ()>); -``` - -The `*mut &'a ()` phantom is deliberate: a raw pointer is *invariant* in its lifetime, so `Life<'a>` is invariant in `'a`. That is correct here — the lifetime is an exact identity in the dependency marker, and a variant `Life` would let the compiler silently coerce one instantiation into another and pick the wrong provider. The macros insert `Life` automatically when a component carries a lifetime; a reader meets it in the generated provider trait, where `IsProviderFor<…, (Life<'a>, T)>` names the lifetime as a type rather than a bare `'a`. Conceptually it joins `Index` (which lifts a `usize`) and `Symbol` (which lifts a string) as another marker making a non-type thing addressable in trait resolution. - -## `MRef<'a, T>`: owned-or-borrowed - -`MRef<'a, T>` is a "maybe-reference" — an enum holding either a borrow of a `T` or an owned `T` — so a single getter signature serves both the context that already stores a value and the one that must produce it. Unlike the rest of this reference, there is nothing type-level about it: it is an ordinary runtime value, the payload a getter hands back. - -```rust -pub enum MRef<'a, T> { Ref(&'a T), Owned(T) } -``` - -A getter declared to return `MRef<'a, T>` lets a context with the value in a field return `MRef::Ref` and lend it, while a context that computes the value returns `MRef::Owned` and gives it away — with no extra cost in the common stored-field case. The caller treats both uniformly because `MRef` derefs to `T`: it implements `Deref` and `AsRef`, builds either variant through `From` and `From<&'a T>`, and promotes a borrow to ownership with `get_or_clone` when `T: Clone`. - -```rust -let stored = String::from("hello"); -let borrowed: MRef<'_, String> = MRef::from(&stored); // lends a stored value -let made: MRef<'_, String> = MRef::from(String::from("world")); // hands over a built one -assert_eq!(&*borrowed, "hello"); -let owned: String = borrowed.get_or_clone(); // clones the borrowed case -``` - -It is one of the getter return modes recognized by the field macros, parallel to `&T`, `Option<&T>`, or `&str`; see [functions and getters](functions-and-getters.md). Its lifetime is an ordinary borrow and is unrelated to the `Life<'a>` lift above. - -## `StaticFormat`: recovering strings and paths - -The type-level encodings need a way back to runtime data, and three traits provide it. `StaticFormat` recovers a type-level string *lazily* by writing into a formatter — it backs the `Display` impls on `Symbol` and `Chars`, recursing down the `Chars` spine to emit each character, so any symbol prints with `to_string()` or `{}`: - -```rust -let s = ::default(); -assert_eq!(s.to_string(), "hello"); -``` - -`StaticString` recovers it *eagerly*, as a compile-time `&'static str` constant: a blanket impl walks the `Chars` list and UTF-8-encodes it into a `[u8; LEN]` at const-evaluation time — which is the consumer that `Symbol`'s `LEN` byte length exists to size — then validates the bytes as a `&'static str`. Use `Display` when a runtime value will do; use `StaticString::VALUE` when a `const` is needed or in a hot path. Both round-trip multi-byte Unicode faithfully: - -```rust -use cgp::core::field::traits::StaticString; -assert_eq!(::VALUE, "世界你好"); -``` - -`ConcatPath` works one level up, joining two `PathCons` paths into one as a pure type-level computation — it keeps each `Head` and splices the second path on where the first reaches `Nil`, the operation behind composing nested accessors: - -```rust -type Joined = >::Output; // the path a.b.c.d -``` - -## Further reference - -Online source-of-truth documents: the [types directory](https://github.com/contextgeneric/cgp/tree/main/docs/reference/types) (`cons.md`, `either.md`, `chars.md`, `index.md`, `field.md`, `life.md`, `mref.md`, `path_cons.md`), the construction macros [`macros/symbol.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/symbol.md), [`macros/product.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/product.md), [`macros/sum.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/sum.md), [`macros/path.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/path.md), and the recovery traits [`traits/static_format.md`](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/static_format.md). diff --git a/docs/skills/cgp/references/wiring.md b/docs/skills/cgp/references/wiring.md deleted file mode 100644 index a991d320..00000000 --- a/docs/skills/cgp/references/wiring.md +++ /dev/null @@ -1,191 +0,0 @@ -# Wiring - -How a context selects which provider implements each of its components, by recording a type-level table that maps every `…Component` marker key to a chosen provider. - -A CGP [component](components.md) splits the consumer trait callers use (`CanDoX`) from the provider trait implementers write (`SomethingDoer`), but that split leaves one question open: for a given context, *which* provider supplies the behavior? Wiring is the answer. It is the act of telling a context, component by component, which provider stands behind each consumer-trait call — and the mechanism is a small type-level lookup table carried on the context type. This file explains that table, the `delegate_components!` macro that populates it, and the `UseContext` provider that lets a provider trait route back through the context's own consumer-trait impl. - -## The table: `DelegateComponent` - -The table is the trait `DelegateComponent`, which maps one key type to one value type: - -```rust -pub trait DelegateComponent { - type Delegate; -} -``` - -There is no method and no data — the trait exists purely to associate a value type (`Delegate`) with a key type (`Key`) on a carrier type (`Self`). The mental model is a type-level key-value map living on the `Self` type, analogous to an object's method table (vtable) in object-oriented languages: where a vtable maps method names to function pointers resolved at runtime, this table maps `…Component` marker types to provider types resolved entirely at compile time. Implementing `DelegateComponent` is "setting" the entry at `Key`; naming `Self: DelegateComponent` in a bound and reading `Self::Delegate` is "getting" the value back out. Because Rust forbids two impls of the same trait for the same `Self` and `Key`, each key maps to exactly one value, which is what makes the structure a genuine map. - -When the key is a component marker such as `GreeterComponent`, a populated entry causes the context to inherit the matching provider trait through the blanket impl, and from there the consumer trait — so `app.greet()` type-checks. The blanket impl's body is itself a `DelegateComponent` lookup: it reads the entry, finds the provider, and forwards the call to it. The lookup *is* the whole routing mechanism. (When the key is some arbitrary type instead of a component marker — a shape, a tag — the same trait serves as a plain dispatch table walked by a higher-order provider, with no provider trait attached; see [higher-order providers](higher-order-providers.md).) - -## `delegate_components!`: populating the table - -You almost never write `DelegateComponent` impls by hand. The `delegate_components!` macro populates the table from a compact `Key: Value` syntax, one entry per component, the marker as the key and the chosen provider as the value: - -```rust -#[derive(HasField)] -pub struct Rectangle { - pub width: f64, - pub height: f64, -} - -delegate_components! { - Rectangle { - AreaCalculatorComponent: RectangleArea, - } -} -``` - -This wires `Rectangle` to use the `RectangleArea` provider for its `AreaCalculatorComponent`. After this, calling `rect.area()` resolves through `Rectangle`'s table to `RectangleArea`. The target before the brace is the [context](checking.md) (or an intermediary provider table) whose table is being defined; each key is a `…Component` marker type and each value is the provider type to delegate it to. - -When several components share one provider, the array form on the key side lets a bracketed list of markers expand to one entry each, all pointing at the same value: - -```rust -delegate_components! { - Rectangle { - [ - AreaCalculatorComponent, - PerimeterCalculatorComponent, - ]: RectangleGeometry, - GreeterComponent: GreetHello, - } -} -``` - -This is exactly equivalent to writing `AreaCalculatorComponent: RectangleGeometry` and `PerimeterCalculatorComponent: RectangleGeometry` on separate lines, plus the `GreeterComponent` entry — three entries in all. - -A `new` keyword in front of the target makes the macro define the target struct as well, saving a separate declaration. `new GeometryComponents { … }` emits `struct GeometryComponents;` alongside the table impls. This is the idiomatic way to declare an **aggregate provider** — a zero-sized provider whose only purpose is to hold a table that dispatches each component to a sub-provider, so that other contexts can delegate a whole group of components to it as a single unit: - -```rust -delegate_components! { - new GeometryComponents { - AreaCalculatorComponent: RectangleArea, - PerimeterCalculatorComponent: RectanglePerimeter, - } -} -``` - -An aggregate provider is a *provider*, not a context. `GeometryComponents` implements each component's provider trait by forwarding through its `DelegateComponent` table, and another context then delegates to it — `delegate_components! { App { [AreaCalculatorComponent, PerimeterCalculatorComponent]: GeometryComponents } }` — reusing the whole bundle in one line. Because it is never its own context, an aggregate provider is always wired with plain `delegate_components!`, never `delegate_and_check_components!`: the checked macro would assert that the aggregate can *use* each component as a context, but `GeometryComponents` has no fields and never implements a provider trait with itself as the context, so that assertion is meaningless and would fail. An aggregate provider is verified instead when a real context that delegates to it is checked; see [checking](checking.md). - -A leading generic list on the target makes the whole table generic, so one wiring applies across a family of contexts: `delegate_components! { MyContext { … } }` wires every `MyContext` at once. - -### What the macro generates - -Each entry expands to a pair of impls. The first is the `DelegateComponent` impl that stores the entry — for the `Rectangle` example above, the macro emits: - -```rust -impl DelegateComponent for Rectangle { - type Delegate = RectangleArea; -} -``` - -This impl alone is what the provider blanket impl reads when it looks the component up. The second impl is an `IsProviderFor` impl that forwards the chosen provider's requirements back through the table, so a missing transitive [impl-side dependency](components.md) still surfaces as a usable compiler error rather than a dead end. The details of that propagation belong to [components](components.md) and [checking](checking.md) — here it is enough to know that wiring an entry makes it both *resolvable* (the `DelegateComponent` half) and *checkable* (the `IsProviderFor` half). - -## Explicit delegation: what wiring effectively does - -To see what `delegate_components!` buys you, it helps to write the routing out by hand. Without any table, a context can implement a consumer trait directly by calling a provider's provider-trait method itself. Given the `AreaCalculator` provider trait (whose method takes the context as an explicit `Context` parameter rather than `&self`), a context can hand-route its `CanCalculateArea` consumer impl through the `RectangleArea` provider: - -```rust -impl CanCalculateArea for Rectangle { - fn area(&self) -> f64 { - >::area(self) - } -} -``` - -This is the manual equivalent of one `delegate_components!` entry: it names the provider explicitly, invokes its provider-trait method, and passes `self` as the context. The single line `AreaCalculatorComponent: RectangleArea` in the table achieves the same routing generically, through the generated `DelegateComponent` impl and the blanket impl that reads it — for every method on the trait, without you writing out each call. Use this explicit form only as a teaching device or in the rare case where you want a context to bypass the table for one trait; the table form is the idiom. - -## Direct implementation of a consumer trait - -A context need not delegate at all. When the behavior is specific to one context and there is no provider worth naming, you can implement the consumer trait directly — plain vanilla Rust, no table involved: - -```rust -impl CanGreet for Person { - fn greet(&self) { - println!("Hello, I am {}", self.name); - } -} -``` - -This bypasses the provider-trait machinery entirely: `person.greet()` calls this impl directly. Direct implementation and table-driven wiring are mutually exclusive for a given component on a given context — the table's blanket impl already supplies the consumer trait, so a hand-written consumer impl would collide with it. Reach for direct implementation when a component has exactly one context-specific behavior and the indirection of a separate provider adds nothing. - -## `UseContext`: routing a provider trait back to the consumer trait - -`UseContext` is a [provider](components.md) — a zero-sized marker struct with no runtime value — that implements *any* provider trait by forwarding its methods back to the context's own consumer-trait implementation: - -```rust -pub struct UseContext; -``` - -It is the exact dual of the consumer-trait blanket impl. That blanket impl runs consumer-to-provider — a context implements `CanGreet` by delegating to whichever provider implements `Greeter` for it. `UseContext` runs the opposite direction: it implements the provider trait `Greeter` by calling whatever `CanGreet` implementation the context already has. `#[cgp_component]` generates a `UseContext` impl of the provider trait for every component, of roughly the shape: - -```rust -impl Greeter for UseContext -where - Context: CanGreet, -{ - fn greet(context: &Context) { - Context::greet(context) - } -} -``` - -So wiring a component to `UseContext` means "use whatever this context already does for this trait." Its purpose is to turn a context's existing consumer-trait impl into a provider that *another* provider can call. The pattern matters most for [higher-order providers](higher-order-providers.md), which take an inner provider as a type parameter and often default it to `UseContext`, so the inner step falls back to the context's own wiring unless an explicit provider is named. - -The one rule to respect is the circular-dependency caveat: never delegate a component to `UseContext` when the context's only impl of that component *is* the delegation itself. Doing so asks the context to implement the consumer trait by delegating to a provider (`UseContext`) that in turn implements the provider trait by calling the consumer trait — a cycle the trait solver cannot resolve, surfacing as an overflow or unsatisfied-bound compile error. `UseContext` is meant to be supplied to another provider as its inner provider, not wired as a context's own delegate for the same component. - -## Other providers you will see in tables: `WithProvider` and `UseDefault` - -Two more provider markers appear in real wiring often enough to recognize on sight. Both are ordinary zero-sized providers wired like any other, but neither carries behavior of its own — one adapts a lower-level provider into a component, and the other selects a component's own default method bodies. - -`WithProvider` adapts a *foundational* provider into the provider a named component expects. CGP has two layers of provider: the component-specific provider traits a context wires (`NameGetter`, `NameTypeProvider`), and a handful of generic, component-agnostic mechanisms beneath them — [`FieldGetter`](functions-and-getters.md) reads *some* field for *some* tag, and [`TypeProvider`](abstract-types.md) supplies *some* abstract type — that do not know which component they serve. `WithProvider` is the adapter that lets one of those foundational providers stand in for a named component by forwarding the component's method to it. You rarely write `WithProvider<…>` in full, because its common cases ship as aliases you *will* see wired: `WithField` and `WithFieldRef` (a `FieldGetter` as a getter component), `WithType` and `WithDelegatedType` (a `TypeProvider` as an abstract-type component), and `WithContext` (the context's own capability). Reading `NameGetterComponent: WithField<…>`, treat it as "this getter is served by the foundational field-getter named inside." - -`UseDefault` is the empty provider that selects a component's *own* default method bodies. A consumer trait may give its methods default bodies exactly as any Rust trait can; when every method is defaulted there is no behavior left for a provider to supply, yet the component still needs *some* provider in the table to be wired. `UseDefault` is the conventional name for that role — but unlike `UseContext` or `UseField`, no macro generates its impl, so the author writes an empty `#[cgp_impl(UseDefault)]` (or an empty impl on the concrete context) whose body falls through to the trait defaults. Wiring a component to `UseDefault` therefore reads as "use the trait's own defaults for this component." - -## Dispatching a component per type with `open` - -When a provider trait carries an extra generic parameter and the right provider depends on which concrete type that parameter is, the choice is made by a second lookup keyed on that parameter rather than on the component marker. The `open` statement inside `delegate_components!` is the way to write that per-value wiring: it folds the per-type entries directly into the context's own table, one provider per concrete value of the dispatch parameter. Given a `CanCalculateArea` consumer trait whose `Shape` parameter selects the area formula, a context wires each shape to its own provider like this: - -```rust -delegate_components! { - MyApp { - open AreaCalculatorComponent; - - @AreaCalculatorComponent.Rectangle: RectangleArea, - @AreaCalculatorComponent.Circle: CircleArea, - } -} -``` - -The leading `open AreaCalculatorComponent;` header opens one or more components for per-value wiring. The braces are optional when opening a single component, so `open AreaCalculatorComponent;` and `open { AreaCalculatorComponent };` are equivalent; to open several components together, list them inside the braces (`open { A, B };`). The header is a *leading* statement: when a single block mixes plain `Component: Provider` mappings with an `open` block, the `open` header must come first, before any plain mappings, or the macro fails to parse. Each subsequent `@Component.Key: Provider` entry then assigns a provider for one value of that component's dispatch parameter: `@AreaCalculatorComponent.Rectangle: RectangleArea` says that when `Shape` is `Rectangle`, `MyApp` calculates area through `RectangleArea`, and the `Circle` line does the same for `Circle`. After this wiring, `MyApp` implements `CanCalculateArea` via `RectangleArea` and `CanCalculateArea` via `CircleArea`. - -Two shorthands keep the entries compact. When several values of the dispatch parameter share one provider, an array on the final path segment expands to one entry each — `@AreaCalculatorComponent.[Rectangle, Circle]: SomeProvider` wires both shapes to `SomeProvider`. When a dispatch value needs generic parameters of its own, they precede the value: `@SomeComponent.<'a, T> &'a T: SomeProvider` dispatches on the type `&'a T` for all `'a` and `T`. - -The `open` form needs no extra macro on the component. It works through the `RedirectLookup` impl that every `#[cgp_component]` already generates, so dispatching a component per type requires no `#[derive_delegate]` on the trait — the same component you wire by marker is the one you open by value. `open` is a lightweight form of the full namespace feature, suited to a context wiring its own components directly; it does not combine with a joined namespace where the component carries `#[prefix(...)]`. The full namespace machinery, including `@`-path keys and the `namespace` statement, is described in [namespaces](namespaces.md). - -### Legacy: `UseDelegate` nested tables - -An older form writes the per-type entries into a separate table wrapped in the `UseDelegate` provider, rather than folding them into the context's own table with `open`. It nests a `new` table inside a single `UseDelegate<…>` value: - -```rust -delegate_components! { - MyApp { - AreaCalculatorComponent: UseDelegate, - } -} -``` - -This desugars `UseDelegate` into a standalone `delegate_components! { new AreaCalculatorComponents { … } }` plus an outer entry `AreaCalculatorComponent: UseDelegate` pointing the component at that inner table. The end effect matches the `open` example above — `MyApp` dispatches `Rectangle` to `RectangleArea` and `Circle` to `CircleArea` — but the dispatch values live in a named side table reached through `UseDelegate` instead of in `MyApp`'s table directly. - -This is a legacy dispatch mechanism, retained for compatibility and expected to be deprecated and removed. It is documented here so that the nested-table form can be *read* where it still appears in existing code; for *writing* new wiring, prefer `open`, which dispatches the same component without the extra `UseDelegate` indirection or the separate inner table. The detailed mechanics of the `UseDelegate` provider — how it reads its inner table at the provider level — belong to [higher-order providers](higher-order-providers.md). - -## Related constructs - -Wiring is the step that connects [components](components.md) — the consumer/provider trait pairs and `…Component` markers — to the providers that implement them, and the `IsProviderFor` impls it generates feed the completeness guarantees described in [checking](checking.md). The `open` statement shown above dispatches a generic-parameter component per value through the `RedirectLookup` impl every component generates; the full namespace feature it derives from, including `@`-path keys and the `namespace` statement, is described in [namespaces](namespaces.md). `UseContext` and the legacy `UseDelegate` provider — including how it reads an inner dispatch table — are covered in [higher-order providers](higher-order-providers.md). - -## Further reference - -Online docs: [delegate_components.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/macros/delegate_components.md), [traits/delegate_component.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/traits/delegate_component.md), [providers/use_context.md](https://github.com/contextgeneric/cgp/blob/main/docs/reference/providers/use_context.md). diff --git a/sibling-projects.md b/sibling-projects.md new file mode 100644 index 00000000..c96bb623 --- /dev/null +++ b/sibling-projects.md @@ -0,0 +1,43 @@ +# Sibling projects + +The Context-Generic Programming ecosystem is split across several repositories that are developed +together, and this file records where each one lives and which revision of it to read. An agent +working here routinely needs one of them — to verify a claim against another project's source, to +revise a document a change here affects, or to keep a fixture and the prose that describes it in +step — so treat this table as the authoritative list of what exists alongside this repository. + +This repository's own documentation lives in the knowledge base, under its `cgp/` directory: the +construct reference, the concepts, the guides, the error catalog, and the macro-implementation notes +all moved there, and the worked examples sit at the knowledge base's top level. + +| Project | Repository | Branch/tag to read | What it is | +|---|---|---|---| +| `cargo-cgp` | | `main` | CGP's first-class toolchain: the cargo subcommand that makes CGP compile errors readable and expands CGP macros. | +| `cgp-skills` | | `main` | The agent skills for CGP, deployed on their own — the `/cgp` skill among them. | +| `cgp-knowledge-base` | | `main` | The consolidated documentation for every CGP project, written by and for AI agents. | + +## Finding a sibling + +Look for a sibling in the parent directory first, at `../`. Every project in this list, +including this one, is expected to sit side by side under one parent directory in a local +environment, so `../cargo-cgp` is the fastest and most current reference — it reflects uncommitted work +that GitHub does not. When the checkout is absent, fetch the file you need from the repository above +instead, at the revision the table records. + +## Reading a sibling versus linking to one + +Reading and linking follow different rules, and conflating them is the mistake to avoid. When you +**read** a document or a source file from a sibling, use the branch or tag this table names, so every +project sees the same revision of the others. When you **link** to one from a committed file here, always +write a GitHub URL on the `main` branch (`https://github.com/contextgeneric//blob/main/`) +rather than a relative `..//...` path, so the link resolves for a reader who has only this +repository checked out. A bare mention of a checkout's location, like the path `../cargo-cgp`, is a +filesystem reference rather than a link and stays relative. + +## Updating a revision + +The branch or tag recorded above changes **only on explicit instruction**, such as when a project +cuts an official release and the ecosystem should pin it. During ordinary development every entry +stays `main`, and it deliberately does *not* track a feature branch in progress: work in flight is +read from the local sibling checkout, not from a revision recorded here. The same rule governs the +`main` in a cross-project link — update it only when told to, as part of a release.