Skip to content

Commit 6674e07

Browse files
authored
Merge pull request #10088 from github/parameterisedModules
parameterised modules in the QL language reference
2 parents 1201592 + cec63e4 commit 6674e07

File tree

4 files changed

+210
-1
lines changed

4 files changed

+210
-1
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Learn all about QL, the powerful query language that underlies the code scanning
1515

1616
- :doc:`Modules <modules>`: Modules provide a way of organizing QL code by grouping together related types, predicates, and other modules.
1717

18+
- :doc:`Signatures <signatures>`: Signatures provide a typing mechanism to parameters of parameterized modules.
19+
1820
- :doc:`Aliases <aliases>`: An alias is an alternative name for an existing QL entity.
1921

2022
- :doc:`Variables <variables>`: Variables in QL are used in a similar way to variables in algebra or logic. They represent sets of values, and those values are usually restricted by a formula.
@@ -44,6 +46,7 @@ Learn all about QL, the powerful query language that underlies the code scanning
4446
queries
4547
types
4648
modules
49+
signatures
4750
aliases
4851
variables
4952
expressions

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

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,92 @@ 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+
.. _parameterized-modules:
137+
138+
Parameterized modules
139+
=====================
140+
141+
Parameterized modules are QL's approach to generic programming.
142+
Similar to explicit modules, parameterized modules are defined within other modules using the keywork ``module``.
143+
In addition to the module name, parameterized modules declare one or more parameters between the name and the module body.
144+
145+
For example, consider the module ``M``, 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 M<transformer/1 first, transformer/1 second> {
151+
bindingset[x]
152+
int applyBoth(int x) {
153+
result = second(first(x))
154+
}
155+
}
156+
157+
Parameterized modules cannot be directly referenced.
158+
Instead, you instantiate a parameterized module by passing arguments enclosed in angle brackets (``<`` and ``>``) to the module.
159+
Instantiated parameterized modules can be used as a :ref:`module expression <name-resolution>`, identical to explicit module references.
160+
161+
For example, we can instantiate ``M`` 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 = M<increment/1, increment/1>;
170+
171+
select IncrementTwice::applyBoth(40) // 42
172+
173+
The parameters of a parameterized 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 parameterized modules is applicative.
183+
That is, if you instantiate a parameterized module twice with identical arguments, the resulting object is the same.
184+
This is particularly relevant for type definitions inside parameterized modules as :ref:`classes <classes>`
185+
or via :ref:`newtype <algebraic-datatypes>`, because the duplication of such type definitions would result in
186+
incompatible types.
187+
188+
The following example instantiates module ``M`` inside calls to predicate ``foo`` twice.
189+
The first call is valid but the second call generates an error.
190+
191+
.. code-block:: ql
192+
193+
bindingset[this]
194+
signature class TSig;
195+
196+
module M<TSig T> {
197+
newtype A = B() or C()
198+
}
199+
200+
string foo(M<int>::A a) { ... }
201+
202+
select foo(M<int>::B()), // valid: repeated identical instantiation of M does not duplicate A, B, C
203+
foo(M<float>::B()) // ERROR: M<float>::B is not compatible with M<int>::A
204+
205+
Module parameters are dependently typed, meaning that signature expressions in parameter definitions can reference
206+
preceding parameters.
207+
208+
For example, we can declare the signature for ``T2`` dependent on ``T1``, enforcing a subtyping relationship
209+
between the two parameters:
210+
211+
.. code-block:: ql
212+
213+
signature class TSig;
214+
215+
module Extends<TSig T> { signature class Type extends T; }
216+
217+
module ParameterizedModule<TSig T1, Extends<T1>::Type T2> { ... }
218+
219+
Dependently typed parameters are particularly useful in combination with
220+
:ref:`parameterized module signatures <parameterized-module-signatures>`.
221+
136222
.. _module-bodies:
137223

138224
Module bodies

docs/codeql/ql-language-reference/name-resolution.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ In summary, the kinds of expressions are:
2121
- **Module expressions**
2222
- These refer to modules.
2323
- They can be simple :ref:`names <names>`, :ref:`qualified references <qualified-references>`
24-
(in import statements), or :ref:`selections <selections>`.
24+
(in import statements), :ref:`selections <selections>`, or :ref:`instantiations <parameterized-modules>`.
2525
- **Type expressions**
2626
- These refer to types.
2727
- They can be simple :ref:`names <names>` or :ref:`selections <selections>`.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
:tocdepth: 1
2+
3+
.. index:: signature
4+
5+
.. _signatures:
6+
7+
Signatures
8+
##########
9+
10+
Parameterized modules use signatures as a type system for their parameters.
11+
There are three categories of signatures: **predicate signatures**, **type signatures**, and **module signatures**.
12+
13+
Predicate signatures
14+
====================
15+
16+
Predicate signatures declare module parameters that will be substituted with predicates when the module is instantiated.
17+
18+
The substitution of predicate signatures relies on structural typing. That is, predicates do not have to be explicitly
19+
defined as implementing a predicate signature - they just have to match the return and argument types.
20+
21+
Predicate signatures are defined much like predicates themselves, but they do not have a body.
22+
In detail, a predicate signature definition consists of:
23+
24+
#. The keyword ``signature``.
25+
#. The keyword ``predicate`` (allows subsitution with a :ref:`predicate without result <predicates-without-result>`),
26+
or the type of the result (allows subsitution with a :ref:`predicate with result <predicates-with-result>`).
27+
#. The name of the predicate signature. This is an `identifier <https://codeql.github.com/docs/ql-language-reference/ql-language-specification/#identifiers>`_
28+
starting with a lowercase letter.
29+
#. The arguments to the predicate signature, if any, separated by commas.
30+
For each argument, specify the argument type and an identifier for the argument variable.
31+
#. A semicolon ``;``.
32+
33+
For example:
34+
35+
.. code-block:: ql
36+
37+
signature int operator(int lhs, int rhs);
38+
39+
Type signatures
40+
===============
41+
42+
Type signatures declare module parameters that will be substituted with types when the module is instantiated.
43+
Type signatures are used to specify supertypes and are the simplest category of signatures.
44+
45+
The substitution of type signatures relies on structural typing. That is, types do not have to be explicitly defined as
46+
implementing a type signature - they just need to have the specified (transitive) supertypes.
47+
48+
In detail, a type signature definition consists of:
49+
50+
#. The keyword ``signature``.
51+
#. The keyword ``class``.
52+
#. The name of the type signature. This is an `identifier <https://codeql.github.com/docs/ql-language-reference/ql-language-specification/#identifiers>`_
53+
starting with a uppercase letter.
54+
#. Optionally, the keyword ``extends`` followed by a list of types, separated by commas.
55+
#. A semicolon ``;``.
56+
57+
For example:
58+
59+
.. code-block:: ql
60+
61+
signature class ExtendsInt extends int;
62+
63+
Module signatures
64+
=================
65+
66+
Module signatures declare module parameters that will be substituted with modules when the module is instantiated.
67+
Module signatures specify a collection of types and predicates that a module needs to contain under given names and
68+
matching given signatures.
69+
70+
Unlike type signatures and predicate signatures, the substitution of type signatures relies on nominal typing.
71+
That is, the definition of a module must declare the module signatures it implements.
72+
73+
In detail, a type signature definition consists of:
74+
75+
#. The keyword ``signature``.
76+
#. The keyword ``module``.
77+
#. The name of the module signature. This is an `identifier <https://codeql.github.com/docs/ql-language-reference/ql-language-specification/#identifiers>`_
78+
starting with a uppercase letter.
79+
#. Optionally, a list of parameters for :ref:`parameterized module signatures <parameterized-module-signatures>`.
80+
#. The module signature body, consisting of type signatures and predicate signatures enclosed in braces.
81+
The ``signature`` keyword is omitted for these contained signatures.
82+
83+
For example:
84+
85+
.. code-block:: ql
86+
87+
signature module MSig {
88+
class T;
89+
predicate restriction(T t);
90+
}
91+
92+
module Module implements MSig {
93+
newtype T = A() or B();
94+
95+
predicate restriction(T t) { t = A() }
96+
}
97+
98+
.. _parameterized-module-signatures:
99+
100+
Parameterized module signatures
101+
-------------------------------
102+
103+
Module signatures can themselves be parameterized in exactly the same way as parameterized modules.
104+
This is particularly useful in combination with the dependent typing of module parameters.
105+
106+
For example:
107+
108+
.. code-block:: ql
109+
110+
signature class NodeSig;
111+
112+
signature module EdgeSig<NodeSig Node> {
113+
predicate apply(Node src, Node dst);
114+
}
115+
116+
module Reachability<NodeSig Node, EdgeSig<Node> Edge> {
117+
Node reachableFrom(Node src) {
118+
Edge::apply+(src, result)
119+
}
120+
}

0 commit comments

Comments
 (0)