Skip to content

Commit 6c08542

Browse files
committed
add "Parameterised modules" section to documentation of modules
1 parent 47353f6 commit 6c08542

1 file changed

Lines changed: 83 additions & 0 deletions

File tree

docs/codeql/ql-language-reference/modules.rst

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,89 @@ defined :ref:`above <library-modules>`:
133133
This defines an explicit module named ``M``. The body of this module defines
134134
the class ``OneTwo``.
135135

136+
.. _parameterised-modules:
137+
138+
Parameterised modules
139+
=====================
140+
141+
Parameterised modules are QL's approach to generic programming.
142+
Similar to explicit modules, parameterised modules are defined within other modules using the keywork ``module``.
143+
In addition to the module name, parameterised modules define one or more parameters between the name and the module body.
144+
145+
For example, consider the module ``ApplyFooThenBar``, which takes two predicate parameters and defines a new predicate
146+
that applies them one after the other:
147+
148+
.. code-block:: ql
149+
150+
module ApplyFooThenBar<transformer/1 foo, transformer/1 bar> {
151+
bindingset[x]
152+
int apply(int x) {
153+
result = bar(foo(x))
154+
}
155+
}
156+
157+
Parameterised modules cannot be directly referenced.
158+
Instead, they are instantiated with arguments passed between ``<`` and ``>``.
159+
Instantiated parameterised modules can be used as a module expression, identical to explicit module references.
160+
161+
For example, we can instantiate ``ApplyFooThenBar`` with two identical arguments ``increment``, creating a module
162+
containing a predicate that adds 2:
163+
164+
.. code-block:: ql
165+
166+
bindingset[result] bindingset[x]
167+
int increment(int x) { result = x + 1 }
168+
169+
module IncrementTwice = ApplyFooThenBar<increment/1, increment/1>;
170+
171+
select IncrementTwice::apply(40) // 42
172+
173+
The parameters of a parameterised module are (meta-)typed with :ref:`signatures <signatures>`.
174+
175+
For example, in the previous two snippets, we relied on the predicate signature ``transformer``:
176+
177+
.. code-block:: ql
178+
179+
bindingset[x]
180+
signature int transformer(int x);
181+
182+
The instantiation of parameterised modules is applicative, meaning that repeated instantiation of a module using
183+
identical arguments results in the same object. This is particularly relevant for type definitions inside parameterised
184+
modules as :ref:`classes <classes>` or via :ref:`newtype <algebraic-datatypes>`.
185+
186+
For example, the following generates an error for the second call to ``foo``, but not for the first:
187+
188+
.. code-block:: ql
189+
190+
bindingset[this]
191+
signature class TSig;
192+
193+
module M<TSig T> {
194+
newtype A = B() or C()
195+
}
196+
197+
string foo(M<int>::A a) { ... }
198+
199+
select foo(M<int>::B()), // valid: repeated identical instantiation of M does not duplicate A, B, C
200+
foo(M<float>::B()) // ERROR: M<float>::B is not compatible with M<int>::A
201+
202+
Module parameters are dependently typed, meaning that signature expressions in parameter definitions can reference
203+
preceding parameters.
204+
205+
For example, we can declare the signature for ``T2`` dependent on ``T1``, enforcing a subtyping relationship
206+
between the two parameters:
207+
208+
.. code-block:: ql
209+
210+
signature class TSig;
211+
212+
module Extends<TSig T> { signature class Type extends T; }
213+
214+
module ParameterisedModule<TSig T1, Extends<T1>::Type T2> { ... }
215+
216+
Dependently typed parameters are particularly useful in combination with
217+
:ref:`parameterised module signatures <parameterised-module-signatures>`.
218+
136219
.. _module-bodies:
137220

138221
Module bodies

0 commit comments

Comments
 (0)