diff --git a/.github/KnowledgeBase/Index.md b/.github/KnowledgeBase/Index.md index 46642602..94585046 100644 --- a/.github/KnowledgeBase/Index.md +++ b/.github/KnowledgeBase/Index.md @@ -557,6 +557,18 @@ It can generate equivalent C++ source files from the the script. ### Design Explanation +#### Attribute System + +Workflow script attributes (`@category:name`) are translated to reflected struct types following the naming convention `system::workflow_attributes::att_category_name`. The compiler resolves, evaluates, and populates attributes onto type descriptors during assembly generation, and the binary serialization format preserves attribute metadata across assembly load/save cycles. Predefined `@cpp:*` attributes control C++ code generation behavior. + +- The naming convention is implemented by `WfLexicalScopeManager::GetWorkflowAttributeTypeName` in `Source/Analyzer/WfAnalyzer.cpp`. +- Predefined attributes include `@cpp:File`, `@cpp:UserImpl`, `@cpp:Private`, `@cpp:Protected`, and `@cpp:Friend`. +- Assembly population is performed by `PopulateAttributesForDeclarations` in `Source/Emitter/WfEmitter_Assembly.cpp`. +- Binary serialization is handled by `IOAttributeBag` in `Source/Runtime/WfRuntimeAssembly.cpp`. +- C++ code generation emits `ATTRIBUTE_TYPE` / `ATTRIBUTE_MEMBER` macros via `WriteAttributeMacro` in `Source/Cpp/WfCpp_WriteReflection.cpp`. + +[Design Explanation](./KB_Workflow_Design_AttributeSystem.md) + ## GacUI Online documentation: https://gaclib.net/doc/current/gacui/home.html diff --git a/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md b/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md index 75141110..93b96dff 100644 --- a/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md +++ b/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md @@ -152,3 +152,8 @@ END_CLASS_MEMBER(MyClass) - `ATTRIBUTE_PARAMETER` raises `CHECK_ERROR` if the named parameter does not exist or is ambiguous. - A `static_assert` fires at compile time if `TYPE{ ARG1, ... }` is not a valid expression. - `CHECK_ERROR` is raised if the attribute type is not a reflected struct or if an argument is not serializable. + +## Workflow Script Attributes + +Workflow script has its own attribute syntax (`@category:name`) that builds on the C++ reflection attribute infrastructure documented above. +For naming conventions, predefined attributes, assembly population, and binary serialization details, see [KB_Workflow_Design_AttributeSystem.md](./KB_Workflow_Design_AttributeSystem.md). diff --git a/.github/KnowledgeBase/KB_Workflow_Design_AttributeSystem.md b/.github/KnowledgeBase/KB_Workflow_Design_AttributeSystem.md new file mode 100644 index 00000000..5b014058 --- /dev/null +++ b/.github/KnowledgeBase/KB_Workflow_Design_AttributeSystem.md @@ -0,0 +1,76 @@ +# Workflow Attribute System + +## How Workflow script attributes map to the C++ reflection attribute infrastructure, including naming conventions, predefined attributes, assembly population, and binary serialization. + +## Attribute Naming Convention + +In Workflow script, attributes are written as `@category:name` (e.g., `@cpp:File`, `@cpp:UserImpl`). +The compiler translates this syntax into a reflected struct type name using a fixed pattern: + +``` +@category:name → system::workflow_attributes::att_category_name +``` + +This translation is performed by `WfLexicalScopeManager::GetWorkflowAttributeTypeName(category, name)` in `Source/Analyzer/WfAnalyzer.cpp`. +For example: +- `@cpp:File` → `system::workflow_attributes::att_cpp_File` +- `@cpp:UserImpl` → `system::workflow_attributes::att_cpp_UserImpl` +- `@test:Int` → `system::workflow_attributes::att_test_Int` + +## Attribute Resolution + +`WfLexicalScopeManager::ResolveWorkflowAttribute(category, name)` in `Source/Analyzer/WfAnalyzer.cpp` resolves an attribute to its `ITypeDescriptor`. +It validates: +- The type must exist and be a struct (`TypeDescriptorFlags::Struct`). +- The struct must have zero or one property. Zero properties means no argument; one property defines the argument type. + +## Predefined Attributes + +The following attributes are predefined in `Source/Library/WfLibraryPredefined.h` under the `vl::__vwsn` namespace. +They are registered in `Source/Library/WfLibraryReflection.cpp` with `IMPL_TYPE_INFO_RENAME` to map to the `system::workflow_attributes::*` type names. + +| Workflow Syntax | Type Name | Argument | Purpose | +|---------------------|-------------------------------------------------|------------------|----------------------------------------------------------| +| `@cpp:File` | `system::workflow_attributes::att_cpp_File` | `WString` | Controls which generated C++ file a declaration goes to | +| `@cpp:UserImpl` | `system::workflow_attributes::att_cpp_UserImpl` | (none) | Marks a method as user-implemented in generated C++ | +| `@cpp:Private` | `system::workflow_attributes::att_cpp_Private` | (none) | Makes a member private in generated C++ | +| `@cpp:Protected` | `system::workflow_attributes::att_cpp_Protected`| (none) | Makes a member protected in generated C++ | +| `@cpp:Friend` | `system::workflow_attributes::att_cpp_Friend` | `ITypeDescriptor*` | Declares a friend class in generated C++ | + +### Non-Serializable Argument Types + +Most attribute arguments are serializable primitive values (`WString`, `vint`, `bool`, `float`, `double`). +`@cpp:Friend` is a special case: its argument type is `ITypeDescriptor*`, which is **not** serializable. +The attribute infrastructure handles this by: +- Storing the raw `ITypeDescriptor*` pointer as a boxed `Value` via `BoxValue(td)`. +- During binary serialization, the `ITypeDescriptor*` is stored as the pointed-to type descriptor's full name (a `WString`), and restored via `GetTypeDescriptor(typeName)` during deserialization. + +## Assembly Population + +When the Workflow compiler generates an assembly from compiled scripts, attributes are extracted from the AST and attached to type descriptors. +This happens in `Source/Emitter/WfEmitter_Assembly.cpp`: + +- **`EvaluateAttributeLiteralExpression(manager, expr, argumentTd)`**: Evaluates a literal AST expression node into a boxed `Value`. Supports `bool`, `WString`, `vint`, `vuint`, `float`, `double`, `typeof(T)` (for `ITypeDescriptor*` arguments), and enum references. +- **`PopulateAttributesOnBag(manager, td, memberInfo, atts)`**: Iterates a list of attribute AST nodes, resolves each via `ResolveWorkflowAttribute`, creates an `AttributeInfoImpl`, evaluates the argument value (if any), and registers the attribute on the type descriptor. +- **`PopulateAttributesForDeclarations(manager)`**: Iterates all `declarationTypes` from the manager. For each type, it populates: + - Type-level attributes from the declaration's attribute list. + - Member-level attributes from class/interface method, property, and event declarations. + - Member-level attributes from struct field declarations. + - For `WfAutoPropertyDeclaration`, attribute lookup falls back to finding the property by name. +- This is called from `GenerateAssembly` after type implementation sorting. + +## Binary Serialization + +The `WfAssembly` binary format includes attribute data so that deserialized assemblies retain attribute metadata. +This is implemented in `Source/Runtime/WfRuntimeAssembly.cpp` as the `IOAttributeBag` helper: + +- **Writer**: For each attribute on a target (type or member), writes the attribute type name, then for each value writes the value's type name and its serialized form. Non-serializable types (e.g., `ITypeDescriptor*`) are serialized by writing the pointed-to type descriptor's full name. +- **Reader**: Reads the attribute type name, creates an `AttributeInfoImpl`, then reads each value. For non-serializable types, reads the type name string and resolves it via `GetTypeDescriptor`. +- `IOAttributeBag` is called for every custom type (`IOCustomType`) and struct (`IOStruct`) during assembly serialization and deserialization, covering type-level, method, event, and property attributes. + +## C++ Code Generation + +When generating C++ reflection registration code, `WriteAttributeMacro` in `Source/Cpp/WfCpp_WriteReflection.cpp` emits the appropriate `ATTRIBUTE_TYPE` and `ATTRIBUTE_MEMBER` macros: +- `WString` arguments are emitted as string literals. +- `ITypeDescriptor*` arguments are emitted as `GetTypeDescriptor(L"typeName")` calls. +- Attributes with no arguments are emitted with just the type name.