diff --git a/daslib/debug.das b/daslib/debug.das index 3186d470af..0e4aa700b4 100644 --- a/daslib/debug.das +++ b/daslib/debug.das @@ -1184,9 +1184,9 @@ class private DAgent : DapiDebugAgent { def override onBreakpoint(var ctx : Context; at : LineInfo; reason, text : string) : void { //! Handles breakpoint or exception events from the runtime, resetting flags on exceptions. if (reason == "exception") { - self->getContextDataFor(ctx) $(var ctxData : DAContext) { + self->getContextDataFor(ctx, $(var ctxData : DAContext) { ctxData |> reset_debug_flags() - } + }) } self->onPause(ctx, at, reason, text) } @@ -1222,7 +1222,7 @@ class private DAgent : DapiDebugAgent { self->log("{ctx} `{reason}` breakpoint at {path}:{int(at.line)}\n") self->addContext(ctx) - self->getContextDataFor(ctx) $(var ctxData : DAContext) { + self->getContextDataFor(ctx, $(var ctxData : DAContext) { ctx |> set_single_step(true) self->sendStopped(ctx, ctxData, path, at, reason, text, -1ul) while (!ctxData.continueRequested && !ctxData.stepInRequested && ctxData.stepRequestedStack == 0) { @@ -1231,7 +1231,7 @@ class private DAgent : DapiDebugAgent { ctxData.continueRequested = false self->afterPause() ctx |> set_single_step(!withInstruments || ctxData.stepInRequested || ctxData.stepRequestedStack > 0) - } + }) } def override onSingleStep(var ctx : Context; at : LineInfo) : void { @@ -1262,7 +1262,7 @@ class private DAgent : DapiDebugAgent { ctx |> set_single_step(!withInstruments) } - self->getContextDataFor(ctx) $(var ctxData : DAContext) { + self->getContextDataFor(ctx, $(var ctxData : DAContext) { let file = string(at.fileInfo.name) |> resolve_path_cache(workingPaths, pathAliases, pathsCache) if (ctxData.pauseRequested) { self->sendStopped(ctx, ctxData, file, at, "pause", "", -1ul) @@ -1334,7 +1334,7 @@ class private DAgent : DapiDebugAgent { } } } - } + }) } def nextStep(var ctx : Context; var ctxData : DAContext; path : string; at : LineInfo) { @@ -1367,7 +1367,7 @@ class private DAgent : DapiDebugAgent { def override onVariable(var ctx : Context; category, name : string; info : TypeInfo; data : void?) : void { //! Collects a debug agent variable into the topmost stack frame under the given category. - self->getContextDataFor(ctx) $(var ctxData : DAContext) { + self->getContextDataFor(ctx, $(var ctxData : DAContext) { let stackLen = length(ctxData.stack) if (stackLen > 0) { var frame & = unsafe(ctxData.stack[0]) @@ -1392,7 +1392,7 @@ class private DAgent : DapiDebugAgent { } frame.state |> emplace <| (STATE_VARS + uint64(length(frame.state)), category, [ variable]) } - } + }) } def sendStopped(var ctx : Context; var ctxData : DAContext; path : string; at : LineInfo; reason, text : string; brId : uint64) { @@ -1464,7 +1464,7 @@ class private DAgent : DapiDebugAgent { } } - let found = self->getContextDataFor(ctx) $(var ctxData : DAContext) {// TODO: speedup getContextDataFor + let found = self->getContextDataFor(ctx, $(var ctxData : DAContext) {// TODO: speedup getContextDataFor *ctxData.ctx |> clear_instruments() *ctxData.ctx |> instrument_node(true) $(ati) { if (ati.fileInfo == null) { @@ -1491,7 +1491,7 @@ class private DAgent : DapiDebugAgent { } return false } - } + }) if (!found) { // removed context, uninitialize related breakpoints @@ -1898,7 +1898,7 @@ class private DAServer : Server { let ctxId = uint64(ctxAndFrame) / MAX_STACK var found = false var valid = false - agent->getContextData(ctxId) $(var ctxData : DAContext) { + agent->getContextData(ctxId, $(var ctxData : DAContext) { let frameId = int(ctxAndFrame % MAX_STACK) if (frameId >= 0 && frameId < length(ctxData.stack)) { let kind = uint64(ini.variablesReference) % MAX_VARIABLES @@ -1946,7 +1946,7 @@ class private DAServer : Server { } } } - } + }) if (!found || !valid) { self->sendSuccessResponse(seq, command, JVNull()) } @@ -2002,7 +2002,7 @@ class private DAServer : Server { //! Handles the DAP stackTrace request, returning stack frames for the specified thread. var inscope ini <- StackTraceArguments(data) var inscope res : StackTraceResponseBody - agent->getContextData(uint64(ini.threadId)) $(var ctxData : DAContext) { + agent->getContextData(uint64(ini.threadId), $(var ctxData : DAContext) { for (idx, line in range(ini.levels), ctxData.stack) { res.stackFrames |> emplace(StackFrame( id = double(ctxData.id * MAX_STACK + uint64(idx)), @@ -2013,7 +2013,7 @@ class private DAServer : Server { )) } res.totalFrames = double(length(ctxData.stack)) - } + }) self->sendSuccessResponseStr(seq, command, sprint_json(res, false)) } @@ -2022,7 +2022,7 @@ class private DAServer : Server { var inscope ini <- ScopesArguments(data) var inscope res : ScopesResponseBody let ctxId = uint64(ini.frameId / double(MAX_STACK)) - agent->getContextData(ctxId) $(var ctxData : DAContext) { + agent->getContextData(ctxId, $(var ctxData : DAContext) { let frameId = int(ini.frameId % double(MAX_STACK)) if (frameId >= 0 && frameId < length(ctxData.stack)) { let spAddr = ctxData.stack[frameId].spAddr @@ -2064,7 +2064,7 @@ class private DAServer : Server { )) } } - } + }) self->sendSuccessResponseStr(seq, command, sprint_json(res, false)) } @@ -2091,7 +2091,7 @@ class private DAServer : Server { } } } else { - agent->getContextData(ctxId) $(var ctxData : DAContext) { + agent->getContextData(ctxId, $(var ctxData : DAContext) { let frameId = int(ctxAndFrame % MAX_STACK) if (frameId >= 0 && frameId < length(ctxData.stack)) { let kind = uint64(ini.variablesReference) % MAX_VARIABLES @@ -2145,7 +2145,7 @@ class private DAServer : Server { } } } - } + }) } self->sendSuccessResponseStr(seq, command, sprint_json(res, false)) // TODO: , false } @@ -2153,51 +2153,51 @@ class private DAServer : Server { def reqContinue(seq : double; command : string; data : JsonValue?) { //! Handles the DAP continue request, resuming execution of the specified thread. var inscope ini <- ContinueArguments(data) - agent->getContextData(uint64(ini.threadId)) $(var ctx : DAContext) { + agent->getContextData(uint64(ini.threadId), $(var ctx : DAContext) { ctx |> reset_debug_flags() ctx.continueRequested = true - } + }) self->sendSuccessResponse(seq, command, null) } def reqPause(seq : double; command : string; data : JsonValue?) { //! Handles the DAP pause request, pausing execution of the specified thread. var inscope ini <- PauseArguments(data) - agent->getContextData(uint64(ini.threadId)) $(var ctx : DAContext) { + agent->getContextData(uint64(ini.threadId), $(var ctx : DAContext) { agent->reqPause(ctx) - } + }) self->sendSuccessResponse(seq, command, null) } def reqStepIn(seq : double; command : string; data : JsonValue?) { //! Handles the DAP stepIn request, stepping into the next statement. var inscope ini <- StepInArguments(data) - agent->getContextData(uint64(ini.threadId)) $(var ctx : DAContext) { + agent->getContextData(uint64(ini.threadId), $(var ctx : DAContext) { ctx |> reset_debug_flags() ctx.stepInRequested = true - } + }) self->sendSuccessResponse(seq, command, null) } def reqNext(seq : double; command : string; data : JsonValue?) { //! Handles the DAP next request, stepping over to the next statement at the current stack depth. var inscope ini <- NextArguments(data) - agent->getContextData(uint64(ini.threadId)) $(var ctx : DAContext) { + agent->getContextData(uint64(ini.threadId), $(var ctx : DAContext) { ctx |> reset_debug_flags() ctx.stepRequestedStack = stack_depth(*ctx.ctx) ctx.stepInRequested = ctx.stepRequestedStack == 0 - } + }) self->sendSuccessResponse(seq, command, null) } def reqStepOut(seq : double; command : string; data : JsonValue?) { //! Handles the DAP stepOut request, stepping out of the current function. var inscope ini <- NextArguments(data) - agent->getContextData(uint64(ini.threadId)) $(var ctx : DAContext) { + agent->getContextData(uint64(ini.threadId), $(var ctx : DAContext) { ctx |> reset_debug_flags() ctx.stepRequestedStack = max(stack_depth(*ctx.ctx) - 1, 0) ctx.stepInRequested = ctx.stepRequestedStack == 0 - } + }) self->sendSuccessResponse(seq, command, null) } @@ -2207,7 +2207,7 @@ class private DAServer : Server { var found = false let ctxId = uint64(ini.frameId / double(MAX_STACK)) - agent->getContextData(ctxId) $(var ctxData : DAContext) { + agent->getContextData(ctxId, $(var ctxData : DAContext) { let frameId = int(ini.frameId % double(MAX_STACK)) if (frameId >= 0 && frameId < length(ctxData.stack)) { let spAddr = ctxData.stack[frameId].spAddr @@ -2294,7 +2294,7 @@ class private DAServer : Server { self->sendSuccessResponseStr(seq, command, sprint_json(res, false)) } } - } + }) if (!found) { self->sendSuccessResponse(seq, command, null) } diff --git a/doc/source/reference/language/macros.rst b/doc/source/reference/language/macros.rst index 659d9c3fca..52ed83c314 100644 --- a/doc/source/reference/language/macros.rst +++ b/doc/source/reference/language/macros.rst @@ -473,6 +473,43 @@ transformations via ``visit(prog, adapter)``. :ref:`tutorial_macro_pass_macro` — step-by-step tutorial with lint and infer macro examples. +------------ +AstTypeMacro +------------ + +``AstTypeMacro`` lets you define custom type expressions resolved during +type inference. It has a single method: + +.. code-block:: das + + class AstTypeMacro { + def abstract visit ( prog:ProgramPtr; mod:Module?; td:TypeDeclPtr; passT:TypeDeclPtr ) : TypeDeclPtr + } + +``add_new_type_macro`` adds a type macro to a module. +The ``[type_macro(name="…")]`` annotation automates registration. + +The compiler parses invocations like ``name(type, N)`` in type position +into a ``TypeDecl`` with ``baseType = Type.typeMacro``. The arguments are +stored in ``td.dimExpr``: + +- ``dimExpr[0]`` — ``ExprConstString`` with the macro name +- ``dimExpr[1..]`` — user arguments (``ExprTypeDecl`` for types, + ``ExprConstInt`` for integers, etc.) + +``visit()`` is called in two contexts: + +- **Concrete** — all types are inferred; ``passT`` is null; + ``dimExpr[i]._type`` is the resolved type. +- **Generic** — type parameters like ``auto(TT)`` are unresolved; + ``passT`` carries the actual argument type for matching; + ``dimExpr[i]._type`` is null. + +.. seealso:: + + :ref:`tutorial_macro_type_macro` — step-by-step tutorial showing + concrete and generic type-macro usage. + ---------------- AstTypeInfoMacro ---------------- diff --git a/doc/source/reference/tutorials.rst b/doc/source/reference/tutorials.rst index 1ff3fde2aa..f27d9f971e 100644 --- a/doc/source/reference/tutorials.rst +++ b/doc/source/reference/tutorials.rst @@ -158,4 +158,6 @@ Run any tutorial from the project root:: tutorials/macros/11_reader_macro.rst tutorials/macros/12_typeinfo_macro.rst tutorials/macros/13_enumeration_macro.rst - tutorials/macros/14_pass_macro.rst \ No newline at end of file + tutorials/macros/14_pass_macro.rst + tutorials/macros/15_type_macro.rst + tutorials/macros/16_template_type_macro.rst \ No newline at end of file diff --git a/doc/source/reference/tutorials/macros/14_pass_macro.rst b/doc/source/reference/tutorials/macros/14_pass_macro.rst index cb47c66d6c..b9cf59be8e 100644 --- a/doc/source/reference/tutorials/macros/14_pass_macro.rst +++ b/doc/source/reference/tutorials/macros/14_pass_macro.rst @@ -353,6 +353,8 @@ The standard library uses pass macros for several purposes: Previous tutorial: :ref:`tutorial_macro_enumeration_macro` + Next tutorial: :ref:`tutorial_macro_type_macro` + Standard library: ``daslib/heartbeat.das``, ``daslib/coverage.das``, ``daslib/lint.das`` diff --git a/doc/source/reference/tutorials/macros/15_type_macro.rst b/doc/source/reference/tutorials/macros/15_type_macro.rst new file mode 100644 index 0000000000..3b4664b836 --- /dev/null +++ b/doc/source/reference/tutorials/macros/15_type_macro.rst @@ -0,0 +1,156 @@ +.. _tutorial_macro_type_macro: + +.. index:: + single: Tutorial; Macros; Type Macro + single: Tutorial; Macros; type_macro + single: Tutorial; Macros; AstTypeMacro + +==================================================== + Macro Tutorial 15: Type Macro +==================================================== + +Previous tutorials showed macros attached to functions, structures, +loops, enums, typeinfo expressions, and entire compilation passes. +**Type macros** operate in a different domain: they let you define +custom type expressions that the compiler resolves during type +inference. + +``AstTypeMacro`` is the base class. It has a single method: + +``visit(prog : ProgramPtr; mod : Module?; td : TypeDeclPtr; passT : TypeDeclPtr) → TypeDeclPtr`` + ``prog`` is the program being compiled. + ``mod`` is the module that registered the macro. + ``td`` is the ``TypeDecl`` node representing the macro invocation — + its ``dimExpr`` array carries the arguments. + ``passT`` is non-null only in a generic context (see below). + Return the resolved ``TypeDeclPtr``, or an empty pointer on error. + +The annotation ``[type_macro(name="…")]`` registers a class as a type +macro. The name string determines the identifier used in type +position:: + + [type_macro(name="padded")] + class PaddedTypeMacro : AstTypeMacro { ... } + +After registration, ``padded(type, 4)`` is a valid type +expression. + + +How the parser stores arguments +=============================== + +When the compiler sees a type-macro invocation like +``padded(type, 4)`` it creates a ``TypeDecl`` with +``baseType = Type.typeMacro`` and populates the ``dimExpr`` array: + +.. list-table:: + :header-rows: 1 + :widths: 20 35 45 + + * - Index + - AST node + - Contains + * - ``dimExpr[0]`` + - ``ExprConstString`` + - The macro name (``"padded"``) + * - ``dimExpr[1]`` + - ``ExprTypeDecl`` + - The first argument — the type (wraps ``type``) + * - ``dimExpr[2]`` + - ``ExprConstInt`` + - The second argument — the size (``4``) + +Type arguments are wrapped in ``ExprTypeDecl``; their inferred result is +available via ``dimExpr[i]._type``. Value arguments keep their +expression type — ``ExprConstInt`` for integer literals, +``ExprConstString`` for strings, and so on. + + +Two inference contexts +====================== + +The compiler calls ``visit()`` in two different contexts: + +**Concrete** — all type arguments are already inferred. + Example: ``var data : padded(type, 4)``. + Here ``td.dimExpr[1]._type`` is the fully-resolved ``float`` type, + and ``passT`` is null. + The macro clones ``_type``, adds a dimension, and returns + ``float[4]``. + +**Generic** — the type includes unresolved type parameters. + Example: ``def foo(arr : padded(type, 4))``. + Here ``td.dimExpr[1]._type`` is null because the type has not + been inferred yet. ``passT`` is non-null and carries the actual + argument type being matched. + The macro must return a type the compiler can use for generic + matching — typically by cloning the ``ExprTypeDecl.typeexpr`` (which + preserves ``auto(TT)``) and adding the dimension. + +.. important:: + + Always check ``td.dimExpr[i]._type == null`` to distinguish the + generic path from the concrete path. In the generic path, fall + back to ``.typeexpr`` or create an ``autoinfer`` type. + + +Section 1 — The macro module +============================= + +The type macro lives in its own module so that it is available via +``require`` — just like macros in previous tutorials. + +.. literalinclude:: ../../../../../tutorials/macros/type_macro_mod.das + :language: das + :caption: tutorials/macros/type_macro_mod.das + +The ``visit`` method first validates that exactly two user arguments +were provided (``dimExpr`` length 3 — the name plus two arguments) and +that the size argument is a constant integer. + +For the **generic path** (``_type == null``): if the first argument is an +``ExprTypeDecl`` (e.g. wrapping ``auto(TT)``), clone its ``.typeexpr``; +otherwise create a pure ``autoinfer`` type. Add the requested dimension +and return. + +For the **concrete path**: clone ``_type`` (the already-inferred type), +add the dimension, and return the final type — e.g. ``float[4]``. + + +Section 2 — Using the type macro +================================= + +.. literalinclude:: ../../../../../tutorials/macros/15_type_macro.das + :language: das + :caption: tutorials/macros/15_type_macro.das + +``test_concrete`` declares ``var data : padded(type, 4)``. +The compiler invokes the macro in concrete mode — ``_type`` is ``float``, +so the macro returns ``float[4]``. ``data`` is a fixed-size array of +4 floats. + +``sum_padded`` declares its parameter as +``padded(type, 4)``. The compiler invokes the macro in +generic mode. When called with ``floats`` (a ``float[4]``), ``TT`` is +deduced as ``float``; when called with ``ints`` (an ``int[4]``), ``TT`` +is deduced as ``int``. + +Running the tutorial:: + + $ daslang tutorials/macros/15_type_macro.das + concrete: data = [[ 1; 2; 3; 4]] + generic: float sum = 100 + generic: int sum = 10 + + +.. seealso:: + + Full source: + :download:`15_type_macro.das <../../../../../tutorials/macros/15_type_macro.das>`, + :download:`type_macro_mod.das <../../../../../tutorials/macros/type_macro_mod.das>` + + Previous tutorial: :ref:`tutorial_macro_pass_macro` + + Next tutorial: :ref:`tutorial_macro_template_type_macro` + + Language reference: :ref:`Macros ` — full macro system documentation diff --git a/doc/source/reference/tutorials/macros/16_template_type_macro.rst b/doc/source/reference/tutorials/macros/16_template_type_macro.rst new file mode 100644 index 0000000000..581a657f48 --- /dev/null +++ b/doc/source/reference/tutorials/macros/16_template_type_macro.rst @@ -0,0 +1,153 @@ +.. _tutorial_macro_template_type_macro: + +.. index:: + single: Tutorial; Macros; Template Type Macro + single: Tutorial; Macros; typemacro_function + single: Tutorial; Macros; Template Structure + single: Tutorial; Macros; Structure Aliases + +==================================================== + Macro Tutorial 16: Template Type Macro +==================================================== + +:ref:`Tutorial 15 ` showed a type macro that +returns an array type. This tutorial goes further — the ``pair`` macro +**generates a structure** with type-parameterized fields, turning +``pair(type, type)`` into a struct with ``first : int`` +and ``second : float``. + +The implementation uses the *manual* approach (without the +``[template_structure]`` shorthand) to expose the mechanics behind +template type resolution: structure aliases, generic inference, and +template class instantiation. + + +Key concepts +============ + +**Structure aliases** — every generated struct stores its type +parameters as aliases (``T1 → int``, ``T2 → float``). On the generic +path the compiler reads them back to deduce ``auto(T1)``, +``auto(T2)``. + +**``[typemacro_function]``** annotation (from ``daslib/typemacro_boost``) +— converts a regular function into an ``AstTypeMacro``. It +auto-generates the class, registers it with ``add_new_type_macro``, and +extracts ``dimExpr`` arguments into the function parameters. The first +two parameters are always ``macroArgument`` (the ``TypeDecl`` node) and +``passArgument`` (non-null in generic context); subsequent parameters +are the user arguments. + +**``TypeMacroTemplateArgument``** — a helper struct that pairs a +template parameter name with its declared type (``argument_type``) and, +after inference, the resolved concrete type (``inferred_type``). + + +Template definition +=================== + +The template struct uses alias names (``T1``, ``T2``) for its fields. +The ``template`` keyword prevents direct instantiation — these aliases +only make sense after the type macro resolves them:: + + struct template Pair { + first : T1 + second : T2 + } + + +Two paths — concrete and generic +================================= + +The type macro function handles two compiler contexts: + +**Concrete** (``passArgument == null``) — the user wrote fully-specified +types like ``pair(type, type)``:: + + 1. verify_arguments — ensure no remaining auto types + 2. template_structure_name — build mangled name "Pair" + 3. find_unique_structure — deduplicate (skip if already exists) + 4. qmacro_template_class — clone the template struct, rename + it, clear the template flag, apply substitution rules + 5. make_typemacro_template_instance — annotate the new struct so + it can be recognized as a Pair instance later + 6. add_structure_aliases — store T1=int, T2=float on the struct + +**Generic** (``passArgument != null``) — a function parameter uses +``auto(…)`` patterns like ``pair(type, type)`` and +the compiler is matching a concrete argument against it:: + + 1. is_typemacro_template_instance — verify the argument is indeed + a Pair instance + 2. infer_struct_aliases — read T1, T2 aliases from the concrete + struct into template_arguments[i].inferred_type + 3. infer_template_types — for each argument, call + infer_generic_type to match auto(T1) against the actual type, + then update_alias_map so the compiler resolves the names + + +Section 1 — The macro module +============================= + +.. literalinclude:: ../../../../../tutorials/macros/template_type_macro_mod.das + :language: das + :caption: tutorials/macros/template_type_macro_mod.das + +The ``[typemacro_function]`` annotation on ``pair`` converts the +function into an ``AstTypeMacro`` registered under the name +``"pair"``. The annotation also generates code that extracts +``dimExpr[1]`` and ``dimExpr[2]`` into the ``T1`` and ``T2`` +``TypeDeclPtr`` parameters. + +The argument names in ``TypeMacroTemplateArgument`` (``"T1"``, +``"T2"``) **must** match the alias names used in the template struct +fields. This is how ``qmacro_template_class`` knows which alias to +substitute when cloning the structure. + + +Section 2 — Using the type macro +================================= + +.. literalinclude:: ../../../../../tutorials/macros/16_template_type_macro.das + :language: das + :caption: tutorials/macros/16_template_type_macro.das + +Three patterns are demonstrated: + +1. **Concrete instantiation** — ``var p : pair(type, type)`` + triggers the concrete path. The macro generates a struct + ``Pair`` with fields ``first : int`` and + ``second : float``. + +2. **Generic parameter** — ``get_first`` and ``get_second`` accept + any ``Pair`` instantiation. The compiler matches ``auto(T1)`` / + ``auto(T2)`` against the stored aliases and deduces the element + types. + +3. **Generic return type** — ``swap_pair`` returns + ``pair(type, type)`` — the swapped types. Both generic + parameter inference and concrete struct generation happen in the + same call. + +Running the tutorial:: + + $ daslang tutorials/macros/16_template_type_macro.das + concrete: first=42 second=3.14 + get_first: 10 + get_second: 2.5 + swapped: first=2.5 second=10 + + +.. seealso:: + + Full source: + :download:`16_template_type_macro.das <../../../../../tutorials/macros/16_template_type_macro.das>`, + :download:`template_type_macro_mod.das <../../../../../tutorials/macros/template_type_macro_mod.das>` + + Previous tutorial: :ref:`tutorial_macro_type_macro` + + Standard library: ``daslib/typemacro_boost.das`` — infrastructure for + template type macros (``TypeMacroTemplateArgument``, + ``infer_template_types``, ``add_structure_aliases``, etc.) + + Language reference: :ref:`Macros ` — full macro system documentation diff --git a/include/daScript/ast/ast.h b/include/daScript/ast/ast.h index 2c71363c08..c8835526b0 100644 --- a/include/daScript/ast/ast.h +++ b/include/daScript/ast/ast.h @@ -863,6 +863,7 @@ namespace das return this; } FunctionPtr arg_type ( int argIndex, const TypeDeclPtr & td ) { + DAS_ASSERTF(!module, "cannot change argument type after function is added to module, use makeExtern/addToModule instead"); arguments[argIndex]->type = td; return this; } diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 571e550d92..a4c0aae144 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -3132,10 +3132,7 @@ namespace das { case Type::tURange: return make_smart(at, cast::to(value)); case Type::tRange64: return make_smart(at, cast::to(value)); case Type::tURange64: return make_smart(at, cast::to(value)); - default: - DAS_FATAL_LOG("unsupported type in makeConstExpression: %s", type->describe().c_str()); - DAS_FATAL_ERROR("unsupported type in makeConstExpression"); - break; + default: DAS_ASSERTF(0, "we should not even be here"); return nullptr; } } diff --git a/tutorials/macros/15_type_macro.das b/tutorials/macros/15_type_macro.das new file mode 100644 index 0000000000..01a28d7889 --- /dev/null +++ b/tutorials/macros/15_type_macro.das @@ -0,0 +1,68 @@ +options gen2 +options no_aot + +// Tutorial 15 — Type Macro (AstTypeMacro) +// +// This tutorial demonstrates how to create and use a custom type macro. +// The `padded` macro defined in type_macro_mod.das resolves the type +// expression `padded(type, N)` into `T[N]`. +// +// Two usage patterns are shown: +// 1. Concrete — fully specified types (`padded(type, 4)`) +// 2. Generic — auto-deduced types (`padded(type, 4)`) + +require type_macro_mod + +// --- Concrete usage --- +// The compiler sees `padded(type, 4)` and calls PaddedTypeMacro.visit(). +// The macro returns float[4], so `data` is a fixed-size array of 4 floats. + +def test_concrete() { + var data : padded(type, 4) + data[0] = 1.0 + data[1] = 2.0 + data[2] = 3.0 + data[3] = 4.0 + print("concrete: data = {data}\n") +} + +// --- Generic usage --- +// When the type parameter uses `auto(TT)` the compiler resolves TT from +// the call site. The macro returns auto(TT)[4] so the compiler can match +// and deduce the element type. + +def sum_padded(arr : padded(type, 4)) : TT { + var total : TT + for (i in range(4)) { + total += arr[i] + } + return total +} + +[export] +def main() { + test_concrete() + + // sum_padded deduces TT = float from the argument type. + var floats : padded(type, 4) + floats[0] = 10.0 + floats[1] = 20.0 + floats[2] = 30.0 + floats[3] = 40.0 + let float_sum = sum_padded(floats) + print("generic: float sum = {float_sum}\n") + + // sum_padded deduces TT = int from the argument type. + var ints : padded(type, 4) + ints[0] = 1 + ints[1] = 2 + ints[2] = 3 + ints[3] = 4 + let int_sum = sum_padded(ints) + print("generic: int sum = {int_sum}\n") +} + +// Expected output: +// concrete: data = [[ 1; 2; 3; 4]] +// generic: float sum = 100 +// generic: int sum = 10 diff --git a/tutorials/macros/16_template_type_macro.das b/tutorials/macros/16_template_type_macro.das new file mode 100644 index 0000000000..b2ee75c59c --- /dev/null +++ b/tutorials/macros/16_template_type_macro.das @@ -0,0 +1,72 @@ +options gen2 +options no_aot + +// Tutorial 16 — Template Type Macro (structure generation) +// +// This tutorial uses the `pair` type macro from template_type_macro_mod +// to create parameterized structures. `pair(type, type)` expands +// to a struct with `first : T1` and `second : T2`. +// +// Three patterns are demonstrated: +// 1. Concrete instantiation — fully specified types +// 2. Generic parameter — auto-deduced type arguments +// 3. Generic return type — the macro in return-type position + +require template_type_macro_mod + +// --- 1. Concrete instantiation --- +// `pair(type, type)` generates a struct `Pair` +// with `first : int` and `second : float`. + +def test_concrete() { + var p : pair(type, type) + p.first = 42 + p.second = 3.14 + print("concrete: first={p.first} second={p.second}\n") +} + +// --- 2. Generic parameter --- +// The function accepts any Pair instantiation. The compiler matches +// `auto(T1)` / `auto(T2)` against the stored structure aliases. + +def get_first(p : pair(type, type)) : T1 { + return p.first +} + +def get_second(p : pair(type, type)) : T2 { + return p.second +} + +// --- 3. Generic return type --- +// The macro can appear in return-type position as well. Here we swap +// the type arguments — `pair(type, type)` — so the returned +// struct has the types reversed. + +def swap_pair(p : pair(type, type)) : pair(type, type) { + var result : pair(type, type) + result.first = p.second + result.second = p.first + return result +} + +[export] +def main() { + test_concrete() + + // Generic parameter: get_first / get_second. + var p : pair(type, type) + p.first = 10 + p.second = 2.5 + print("get_first: {get_first(p)}\n") + print("get_second: {get_second(p)}\n") + + // Generic return type: swap_pair. + var swapped = swap_pair(p) + print("swapped: first={swapped.first} second={swapped.second}\n") +} + +// Expected output: +// concrete: first=42 second=3.14 +// get_first: 10 +// get_second: 2.5 +// swapped: first=2.5 second=10 diff --git a/tutorials/macros/template_type_macro_mod.das b/tutorials/macros/template_type_macro_mod.das new file mode 100644 index 0000000000..d31535805e --- /dev/null +++ b/tutorials/macros/template_type_macro_mod.das @@ -0,0 +1,121 @@ +options gen2 +options no_aot + +// Tutorial macro module: template type macro (structure generation). +// +// This module defines a `pair` type macro that produces a structure +// with two fields — `first : T1` and `second : T2`. The macro is +// implemented manually (without the `[template_structure]` shorthand) +// to show the mechanics behind template type resolution: +// +// 1. `[typemacro_function]` annotation converts a regular function +// into an AstTypeMacro. It also auto-extracts the dimExpr +// arguments into the function parameters. +// +// 2. On the **concrete** path (`passArgument == null`) the macro +// generates a new structure via `qmacro_template_class`, gives +// it a mangled name like `Pair`, and stores each type +// parameter as a **structure alias** — these aliases tell the +// compiler how to resolve `T1` and `T2` inside the cloned struct. +// +// 3. On the **generic** path (`passArgument != null`) the macro +// reads the aliases back from the concrete struct that was passed +// as the argument, then uses `infer_template_types` to match +// `auto(T1)` / `auto(T2)` against the actual alias values and +// update the compiler's alias map. + +module template_type_macro_mod + +require daslib/typemacro_boost + +// --------------------------------------------------------------------------- +// Template definition. +// +// `struct template` prevents direct instantiation — fields use alias +// names (T1, T2) that only make sense after template substitution. +// --------------------------------------------------------------------------- + +struct template Pair { + first : T1 + second : T2 +} + +// --------------------------------------------------------------------------- +// Type macro function. +// +// `[typemacro_function]` generates the AstTypeMacro boilerplate: +// • Creates a class derived from AstTypeMacro +// • Registers it with add_new_type_macro(name="pair") +// • Auto-extracts dimExpr arguments into function parameters +// (macroArgument = td, passArgument = passT, then user args) +// --------------------------------------------------------------------------- + +[typemacro_function] +def pair(macroArgument, passArgument : TypeDeclPtr; T1, T2 : TypeDeclPtr) : TypeDeclPtr { + // Get the TypeDecl that points to our template struct. + var inscope template_type <- typeinfo ast_typedecl(type) + + // Describe the template arguments — names must match the alias + // names used in the struct fields (T1, T2). + var inscope template_arguments <- [ + TypeMacroTemplateArgument(name = "T1", argument_type <- clone_type(T1)), + TypeMacroTemplateArgument(name = "T2", argument_type <- clone_type(T2)) + ] + + // No extra (non-type) arguments for this macro. + var inscope extra_template_arguments : array> + + // --- generic path --- + // The compiler passes a concrete type it wants to match against + // our template signature. For example, when a function parameter + // is `pair(type, type)` and the caller provides + // a `Pair`, passArgument is the Pair type. + if (passArgument != null) { + // Verify the concrete type is an instance of our Pair template. + if (!is_typemacro_template_instance(passArgument, template_type, extra_template_arguments)) { + return <- TypeDeclPtr() + } + // Read the stored aliases (T1, T2) from the concrete struct + // back into template_arguments[i].inferred_type. + if (!infer_struct_aliases(passArgument.structType, template_arguments)) { + return <- TypeDeclPtr() + } + // Match the inferred concrete types against the template + // parameters (which may contain auto(...) patterns) and + // update the compiler's alias map for further resolution. + return <- infer_template_types(passArgument, template_arguments) + } + + // --- concrete path --- + // passArgument is null — the user wrote a concrete type like + // `pair(type, type)`. We need to generate (or + // look up) the corresponding structure. + + // All type arguments must be fully resolved (no remaining auto). + if (!verify_arguments(template_arguments)) { + return <- TypeDeclPtr() + } + + // Build a mangled name like "Pair" for deduplication. + var struct_name = template_structure_name(template_type.structType, template_arguments, extra_template_arguments) + + // If this exact instantiation already exists, reuse it. + var existing_struct = compiling_program().find_unique_structure(struct_name) + if (existing_struct != null) { + return <- new TypeDecl(baseType = Type.tStructure, structType = existing_struct, at = template_type.at) + } + + // Clone the template struct, rename it, clear the template flag, + // and apply substitution rules so field types resolve correctly. + var inscope resType <- qmacro_template_class(struct_name, type) + + // Annotate the new struct so `is_typemacro_template_instance` + // can identify it as an instance of Pair later (for generics). + make_typemacro_template_instance(resType.structType, template_type.structType, extra_template_arguments) + + // Store T1 and T2 as structure aliases — this is how + // `infer_struct_aliases` will read them back on the generic path. + add_structure_aliases(resType.structType, template_arguments) + + return <- resType +} diff --git a/tutorials/macros/type_macro_mod.das b/tutorials/macros/type_macro_mod.das new file mode 100644 index 0000000000..98b3b6e5cd --- /dev/null +++ b/tutorials/macros/type_macro_mod.das @@ -0,0 +1,70 @@ +options gen2 +options no_aot + +// Tutorial macro module: type macro (AstTypeMacro). +// +// AstTypeMacro lets you define custom type expressions that the compiler +// resolves during type inference. It has a single method: +// visit(prog : ProgramPtr; mod : Module?; td : TypeDeclPtr; passT : TypeDeclPtr) : TypeDeclPtr +// +// When the compiler sees `padded(type, 4)` in a type position it: +// 1. Parses it into a TypeDecl with baseType = Type.typeMacro +// 2. Stores the arguments in `td.dimExpr`: +// td.dimExpr[0] — ExprConstString with the macro name ("padded") +// td.dimExpr[1] — the type argument (ExprTypeDecl wrapping type) +// td.dimExpr[2] — the size argument (ExprConstInt with value 4) +// 3. Calls visit() so the macro can return the resolved type +// +// The `passT` parameter is non-null only in a generic context — it +// carries the actual argument type being matched against the parameter. +// In a concrete declaration (`var x : padded(type, 4)`) passT +// is null and td.dimExpr[1]._type is already inferred. + +module type_macro_mod + +require ast +require daslib/ast_boost +require daslib/templates_boost + +[type_macro(name="padded")] +class PaddedTypeMacro : AstTypeMacro { + //! Type macro that resolves `padded(type, N)` to `T[N]`. + def override visit(prog : ProgramPtr; mod : Module?; td : TypeDeclPtr; passT : TypeDeclPtr) : TypeDeclPtr { + // --- argument validation --- + // dimExpr must have exactly 3 entries: name + type + size. + if (length(td.dimExpr) != 3) { + macro_error(compiling_program(), td.at, "padded expects 2 arguments: type and size") + return <- TypeDeclPtr() + } + // The size argument must be a constant integer. + if (!(td.dimExpr[2] is ExprConstInt)) { + macro_error(compiling_program(), td.at, "padded: second argument must be a constant integer") + return <- TypeDeclPtr() + } + let count = (td.dimExpr[2] as ExprConstInt).value + + // --- generic path --- + // When the type argument is not yet inferred (e.g. in a generic + // parameter like `padded(type, 4)`) we return a type + // with autoinfer so the compiler can match and deduce TT. + if (td.dimExpr[1]._type == null) { + var inscope auto_type : TypeDeclPtr + if (td.dimExpr[1] is ExprTypeDecl) { + // Clone the unresolved type expression (e.g. auto(TT)). + auto_type |> move_new <| clone_type((td.dimExpr[1] as ExprTypeDecl).typeexpr) + } else { + // Fallback: pure auto-infer. + auto_type |> move_new <| new TypeDecl(baseType = Type.autoinfer) + } + auto_type.dim |> push(count) + return <- auto_type + } + + // --- concrete path --- + // The type argument is fully inferred. Clone it and add the + // dimension to produce the final array type (e.g. float[4]). + var inscope final_type <- clone_type(td.dimExpr[1]._type) + final_type.dim |> push(count) + return <- final_type + } +}