[gcc/devel/c++-modules] Constrained partial specializations
Nathan Sidwell
nathan@gcc.gnu.org
Wed Jun 24 20:35:24 GMT 2020
https://gcc.gnu.org/g:eb150e498d05088bf4b7a193d610e92d9425eca7
commit eb150e498d05088bf4b7a193d610e92d9425eca7
Author: Nathan Sidwell <nathan@acm.org>
Date: Wed Jun 24 13:35:06 2020 -0700
Constrained partial specializations
gcc/cp/
* module.cc (depset::hash::add_partial_redirect): Add slot parm.
(enum merge_kind): Add MK_partial.
(merge_kind_name): Likewise.
(trees_out::decl_node): A redirect may be for an EK_DECL.
(trees_out::get_merge_kind): Determine MK_partial.
(trees_{in,out}::key_mergeable): Deal with MK_partial.
(depset::hash::make_dependency): Deal with discovering a partial
specialization.
* pt.c (maybe_new_partial_specialization): Add module bits to the
new typedef.
(tsubst_template_decl): Relax module import assert.
(tsubst_template_decl): Refactor, add fixme to check.
gcc/testsuite/
* g++.dg/modules/nested-constr-1.h: New.
* g++.dg/modules/nested-constr-1_a.H: New.
* g++.dg/modules/nested-constr-1_b.C: New.
* g++.dg/modules/nested-constr-2_a.C: New.
* g++.dg/modules/nested-constr-2_b.C: New.
* g++.dg/modules/nested-constr-2_c.C: New.
* g++.dg/modules/tmpl-part-req-1.h: New.
* g++.dg/modules/tmpl-part-req-1_a.H: New.
* g++.dg/modules/tmpl-part-req-1_b.C: New.
* g++.dg/modules/tmpl-part-req-2.h: New.
* g++.dg/modules/tmpl-part-req-2_a.H: New.
* g++.dg/modules/tmpl-part-req-2_b.C: New.
Diff:
---
ChangeLog.modules | 30 ++++++
gcc/cp/module.cc | 123 +++++++++++++++++++----
gcc/cp/pt.c | 20 +++-
gcc/testsuite/g++.dg/modules/nested-constr-1.h | 15 +++
gcc/testsuite/g++.dg/modules/nested-constr-1_a.H | 3 +
gcc/testsuite/g++.dg/modules/nested-constr-1_b.C | 13 +++
gcc/testsuite/g++.dg/modules/nested-constr-2_a.C | 18 ++++
gcc/testsuite/g++.dg/modules/nested-constr-2_b.C | 11 ++
gcc/testsuite/g++.dg/modules/nested-constr-2_c.C | 5 +
gcc/testsuite/g++.dg/modules/tmpl-part-req-1.h | 15 +++
gcc/testsuite/g++.dg/modules/tmpl-part-req-1_a.H | 3 +
gcc/testsuite/g++.dg/modules/tmpl-part-req-1_b.C | 6 ++
gcc/testsuite/g++.dg/modules/tmpl-part-req-2.h | 24 +++++
gcc/testsuite/g++.dg/modules/tmpl-part-req-2_a.H | 3 +
gcc/testsuite/g++.dg/modules/tmpl-part-req-2_b.C | 6 ++
15 files changed, 271 insertions(+), 24 deletions(-)
diff --git a/ChangeLog.modules b/ChangeLog.modules
index ab0d717a3dc..bd9fc065dd7 100644
--- a/ChangeLog.modules
+++ b/ChangeLog.modules
@@ -1,3 +1,33 @@
+2020-06-24 Nathan Sidwell <nathan@acm.org>
+
+ Constrained partial specializations
+ gcc/cp/
+ * module.cc (depset::hash::add_partial_redirect): Add slot parm.
+ (enum merge_kind): Add MK_partial.
+ (merge_kind_name): Likewise.
+ (trees_out::decl_node): A redirect may be for an EK_DECL.
+ (trees_out::get_merge_kind): Determine MK_partial.
+ (trees_{in,out}::key_mergeable): Deal with MK_partial.
+ (depset::hash::make_dependency): Deal with discovering a partial
+ specialization.
+ * pt.c (maybe_new_partial_specialization): Add module bits to the
+ new typedef.
+ (tsubst_template_decl): Relax module import assert.
+ (tsubst_template_decl): Refactor, add fixme to check.
+ gcc/testsuite/
+ * g++.dg/modules/nested-constr-1.h: New.
+ * g++.dg/modules/nested-constr-1_a.H: New.
+ * g++.dg/modules/nested-constr-1_b.C: New.
+ * g++.dg/modules/nested-constr-2_a.C: New.
+ * g++.dg/modules/nested-constr-2_b.C: New.
+ * g++.dg/modules/nested-constr-2_c.C: New.
+ * g++.dg/modules/tmpl-part-req-1.h: New.
+ * g++.dg/modules/tmpl-part-req-1_a.H: New.
+ * g++.dg/modules/tmpl-part-req-1_b.C: New.
+ * g++.dg/modules/tmpl-part-req-2.h: New.
+ * g++.dg/modules/tmpl-part-req-2_a.H: New.
+ * g++.dg/modules/tmpl-part-req-2_b.C: New.
+
2020-06-11 Nathan Sidwell <nathan@acm.org>
Merge master b825a228907
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 4d6654286b7..5b11cfe4d51 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2516,7 +2516,7 @@ public:
void add_namespace_context (depset *, tree ns);
private:
- depset *add_partial_redirect (depset *partial);
+ depset *add_partial_redirect (depset *partial, depset **slot = nullptr);
bool add_binding (tree ns, tree value);
public:
@@ -2838,7 +2838,7 @@ enum merge_kind
MK_indirect_lwm = 0x5,
MK_local_friend = MK_indirect_lwm, /* Found by CTX, index. */
MK_enum, /* Found by CTX, & 1stMemberNAME. */
- MK_attached, /* Found by attachee & index. */
+ MK_attached, /* Found by attachee & index. */
/* Template specialization kinds below. These are all found via
primary template and specialization args. */
@@ -2861,7 +2861,10 @@ enum merge_kind
MK_alias_spec = MK_decl_spec | MK_tmpl_alias_mask,
- MK_hwm = 16
+ // FIXME: perhaps the MK_field, MK_vtable, MK_as_base can all be
+ // combined, so that this can be put up there?
+ MK_partial = 16,
+ MK_hwm
};
/* This is more than a debugging array. NULLs are used to determine
an invalid merge_kind number. */
@@ -2872,7 +2875,8 @@ static char const *const merge_kind_name[MK_hwm] =
"type spec", "type tmpl spec", /* 8,9 type (template). */
NULL, "type partial spec", /* 10,11 partial template. */
"decl spec", "decl tmpl spec", /* 12,13 decl (template). */
- "alias spec", NULL /* 14,15 alias. */
+ "alias spec", NULL, /* 14,15 alias. */
+ "partial", /* 16 constrained partial */
};
/* Mergeable entity location data. */
@@ -7368,7 +7372,12 @@ trees_in::install_entity (tree decl)
{
DECL_MODULE_PENDING_MEMBERS_P (decl) = true;
if (TREE_CODE (decl) == TEMPLATE_DECL)
- DECL_MODULE_PENDING_MEMBERS_P (DECL_TEMPLATE_RESULT (decl)) = true;
+ {
+ // FIXME: DECL_MODULE_PENDING_MEMBERS_P requires a
+ // TYPE_DECL, what was I thinking?
+ gcc_unreachable ();
+ DECL_MODULE_PENDING_MEMBERS_P (DECL_TEMPLATE_RESULT (decl)) = true;
+ }
}
return true;
@@ -8286,9 +8295,7 @@ trees_out::decl_node (tree decl, walk_kind ref)
/* The DECL_TEMPLATE_RESULT of a partial specialization.
Write the partial specialization's template. */
depset *redirect = dep->deps[0];
- gcc_checking_assert ((redirect->get_entity_kind ()
- == depset::EK_SPECIALIZATION)
- && redirect->is_partial ());
+ gcc_checking_assert (redirect->is_partial ());
tpl = redirect->get_entity ();
goto partial_template;
}
@@ -9867,6 +9874,11 @@ trees_out::get_merge_kind (tree decl, depset *dep)
gcc_unreachable ();
case depset::EK_DECL:
+ if (dep->is_partial ())
+ {
+ mk = MK_partial;
+ break;
+ }
{
tree ctx = CP_DECL_CONTEXT (decl);
@@ -10196,6 +10208,14 @@ trees_out::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
key.index = ix;
}
break;
+
+ case MK_partial:
+ {
+ key.constraints = get_constraints (inner);
+ key.ret = CLASSTYPE_TI_TEMPLATE (TREE_TYPE (inner));
+ key.args = CLASSTYPE_TI_ARGS (TREE_TYPE (inner));
+ }
+ break;
}
tree_node (name);
@@ -10205,15 +10225,17 @@ trees_out::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
u (code);
}
- if (mk == MK_named && inner && TREE_CODE (inner) == FUNCTION_DECL)
+ if (mk == MK_partial
+ || (mk == MK_named && inner && TREE_CODE (inner) == FUNCTION_DECL))
{
tree_node (key.ret);
tree arg = key.args;
- while (arg && arg != void_list_node)
- {
- tree_node (TREE_VALUE (arg));
- arg = TREE_CHAIN (arg);
- }
+ if (mk == MK_named)
+ while (arg && arg != void_list_node)
+ {
+ tree_node (TREE_VALUE (arg));
+ arg = TREE_CHAIN (arg);
+ }
tree_node (arg);
tree_node (key.constraints);
}
@@ -10397,12 +10419,14 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
key.ref_q = cp_ref_qualifier ((code >> 0) & 3);
key.index = code >> 2;
- if (mk == MK_named && inner && TREE_CODE (inner) == FUNCTION_DECL)
+ if (mk == MK_partial
+ || (mk == MK_named && inner && TREE_CODE (inner) == FUNCTION_DECL))
{
key.ret = tree_node ();
tree arg, *arg_ptr = &key.args;
while ((arg = tree_node ())
- && arg != void_list_node)
+ && arg != void_list_node
+ && mk != MK_partial)
{
*arg_ptr = tree_cons (NULL_TREE, arg, NULL_TREE);
arg_ptr = &TREE_CHAIN (*arg_ptr);
@@ -10414,7 +10438,7 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
if (get_overrun ())
return error_mark_node;
- if (mk < MK_indirect_lwm)
+ if (mk < MK_indirect_lwm || mk == MK_partial)
{
DECL_NAME (decl) = name;
DECL_CONTEXT (decl) = FROB_CONTEXT (container);
@@ -10425,6 +10449,26 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
DECL_CONTEXT (inner) = DECL_CONTEXT (decl);
}
+ if (mk == MK_partial)
+ {
+ for (tree spec = DECL_TEMPLATE_SPECIALIZATIONS (key.ret);
+ spec; spec = TREE_CHAIN (spec))
+ {
+ tree tmpl = TREE_VALUE (spec);
+ if (template_args_equal (key.args,
+ CLASSTYPE_TI_ARGS (TREE_TYPE (tmpl)))
+ && cp_tree_equal (key.constraints,
+ get_constraints
+ (DECL_TEMPLATE_RESULT (tmpl))))
+ {
+ existing = tmpl;
+ break;
+ }
+ }
+ if (!existing)
+ add_mergeable_specialization (key.ret, key.args, decl, 2);
+ }
+ else
switch (TREE_CODE (container))
{
default:
@@ -10884,7 +10928,7 @@ trees_in::find_duplicate (tree existing)
return duplicates->get (existing);
}
-/* We're starting to read duplicate DECL. EXISTING is the already
+/* We're starting to read a duplicate DECL. EXISTING is the already
known node. */
void
@@ -11855,6 +11899,44 @@ depset::hash::make_dependency (tree decl, entity_kind ek)
non-DECL entries, or when discovering dependencies. */
gcc_checking_assert (ek != EK_DECL || current);
+ if (DECL_IMPLICIT_TYPEDEF_P (decl)
+ /* ... not an enum, for instance. */
+ && RECORD_OR_UNION_TYPE_P (TREE_TYPE (decl))
+ && TYPE_LANG_SPECIFIC (TREE_TYPE (decl))
+ && CLASSTYPE_USE_TEMPLATE (TREE_TYPE (decl)) == 2)
+ {
+ /* A partial or explicit specialization. Partial
+ specializations constrained by requires clauses are not
+ in the hash table, because they have the same set of
+ template parameters as their general template:
+
+ template<typename T> class silly;
+ template<typename T> requires true class silly {};
+
+ We need to find them, insert their TEMPLATE_DECL in the
+ dep_hash, and then convert the dep we just found into a
+ redirect. */
+
+ tree ti = TYPE_TEMPLATE_INFO (TREE_TYPE (decl));
+ tree tmpl = TI_TEMPLATE (ti);
+ tree partial = NULL_TREE;
+ for (tree spec = DECL_TEMPLATE_SPECIALIZATIONS (tmpl);
+ spec; spec = TREE_CHAIN (spec))
+ if (DECL_TEMPLATE_RESULT (TREE_VALUE (spec)) == decl)
+ {
+ partial = TREE_VALUE (spec);
+ break;
+ }
+
+ if (partial)
+ {
+ depset *tmpl_dep = make_dependency (partial, EK_DECL);
+
+ gcc_checking_assert (tmpl_dep->get_entity_kind () == EK_DECL);
+ return add_partial_redirect (tmpl_dep, slot);
+ }
+ }
+
bool has_def = ek != EK_USING && has_definition (decl);
if (ek > EK_BINDING)
ek = EK_DECL;
@@ -12309,7 +12391,7 @@ specialization_cmp (const void *a_, const void *b_)
dependency add. */
depset *
-depset::hash::add_partial_redirect (depset *partial)
+depset::hash::add_partial_redirect (depset *partial, depset **slot)
{
partial->set_flag_bit<DB_PARTIAL_BIT> ();
@@ -12319,7 +12401,8 @@ depset::hash::add_partial_redirect (depset *partial)
/* Redirects are never reached -- always snap to their target. */
redirect->set_flag_bit<DB_UNREACHED_BIT> ();
- depset **slot = entity_slot (inner, true);
+ if (!slot)
+ slot = entity_slot (inner, true);
gcc_checking_assert (!*slot);
*slot = redirect;
redirect->deps.safe_push (partial);
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index 8062047e86d..0e8334f1a72 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -952,6 +952,9 @@ maybe_new_partial_specialization (tree type)
TREE_PRIVATE (d) = (current_access_specifier == access_private_node);
TREE_PROTECTED (d) = (current_access_specifier == access_protected_node);
+ set_instantiating_module (d);
+ DECL_MODULE_EXPORT_P (d) = DECL_MODULE_EXPORT_P (tmpl);
+
return t;
}
@@ -14152,7 +14155,13 @@ tsubst_template_decl (tree t, tree args, tsubst_flags_t complain,
if (DECL_LANG_SPECIFIC (inner))
{
DECL_MODULE_PURVIEW_P (r) = DECL_MODULE_PURVIEW_P (inner);
- gcc_checking_assert (!DECL_MODULE_IMPORT_P (inner));
+ /* If this is a constrained template, the above tsubst of
+ inner can find the unconstrained template, which may have
+ come from an import. This is ok, because we don't
+ register this instantiation (see below). */
+ gcc_checking_assert (!DECL_MODULE_IMPORT_P (inner)
+ || (TEMPLATE_PARMS_CONSTRAINTS
+ (DECL_TEMPLATE_PARMS (t))));
DECL_MODULE_IMPORT_P (r) = false;
}
}
@@ -14163,13 +14172,16 @@ tsubst_template_decl (tree t, tree args, tsubst_flags_t complain,
if (PRIMARY_TEMPLATE_P (t))
DECL_PRIMARY_TEMPLATE (r) = r;
- if (TREE_CODE (decl) != TYPE_DECL && !VAR_P (decl)
- && !lambda_fntype)
+ if (TREE_CODE (decl) == FUNCTION_DECL && !lambda_fntype)
/* Record this non-type partial instantiation. */
+ // FIXME: Should we be registering this if this is a constrained
+ // template? DECL_TEMPLATE_RESULT (r) might be the unconstrained
+ // template's result we found in the above instantiations. And
+ // that smells wrong.
register_specialization (r, t,
DECL_TI_ARGS (DECL_TEMPLATE_RESULT (r)),
false, hash);
-
+
return r;
}
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-1.h b/gcc/testsuite/g++.dg/modules/nested-constr-1.h
new file mode 100644
index 00000000000..6962ccad850
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-1.h
@@ -0,0 +1,15 @@
+
+template<typename T>
+struct traits
+{
+ template<typename U>
+ struct nested
+ { using type = void; };
+
+ template<typename U> requires requires { typename U::type; }
+ struct nested<U>
+ { using type = typename U::type; };
+};
+
+using V = traits<char>::nested<int>::type;
+
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-1_a.H b/gcc/testsuite/g++.dg/modules/nested-constr-1_a.H
new file mode 100644
index 00000000000..9398986dd42
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-1_a.H
@@ -0,0 +1,3 @@
+// { dg-additional-options "-std=c++2a -fmodule-header" }
+// { dg-module-cmi {} }
+#include "nested-constr-1.h"
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-1_b.C b/gcc/testsuite/g++.dg/modules/nested-constr-1_b.C
new file mode 100644
index 00000000000..7043ffa8554
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-1_b.C
@@ -0,0 +1,13 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts -fno-module-lazy -fdump-lang-module-alias" }
+
+#include "nested-constr-1.h"
+import "nested-constr-1_a.H";
+
+struct X
+{
+ using type = int;
+};
+
+traits<char>::nested<X>::type b;
+
+// { dg-final { scan-lang-dump-not {merge key \(new\)} module } }
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-2_a.C b/gcc/testsuite/g++.dg/modules/nested-constr-2_a.C
new file mode 100644
index 00000000000..b29d670a570
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-2_a.C
@@ -0,0 +1,18 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts" }
+export module foo;
+// { dg-module-cmi foo }
+
+export template<typename T>
+struct traits
+{
+ template<typename U>
+ struct nested
+ { using type = void; };
+
+ template<typename U> requires requires { typename U::type; }
+ struct nested<U>
+ { using type = typename U::type; };
+};
+
+export using V = traits<char>::nested<int>::type;
+
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-2_b.C b/gcc/testsuite/g++.dg/modules/nested-constr-2_b.C
new file mode 100644
index 00000000000..a34ef523f90
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-2_b.C
@@ -0,0 +1,11 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts" }
+export module bar;
+// { dg-module-cmi bar }
+import foo;
+
+struct X
+{
+ using type = int;
+};
+
+export traits<char>::nested<X>::type b;
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-2_c.C b/gcc/testsuite/g++.dg/modules/nested-constr-2_c.C
new file mode 100644
index 00000000000..5c81b3b6c09
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-2_c.C
@@ -0,0 +1,5 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts" }
+
+import bar;
+
+decltype(b) c;
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-1.h b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1.h
new file mode 100644
index 00000000000..09de42923da
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1.h
@@ -0,0 +1,15 @@
+
+template<typename _Iterator, typename>
+struct Trait;
+
+template<typename _Iterator>
+struct Trait<_Iterator, void> {};
+
+template<typename _Iterator>
+requires true
+struct Trait<_Iterator, void>
+{
+ template<typename _Iter> struct __diff {};
+
+ template<typename _Iter> requires true struct __diff<_Iter> {};
+};
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_a.H b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_a.H
new file mode 100644
index 00000000000..20804d73620
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_a.H
@@ -0,0 +1,3 @@
+// { dg-additional-options "-fmodule-header -std=c++2a" }
+// { dg-module-cmi {} }
+#include "tmpl-part-req-1.h"
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_b.C b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_b.C
new file mode 100644
index 00000000000..4c00d54aee9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_b.C
@@ -0,0 +1,6 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts -fno-module-lazy -fdump-lang-module-alias" }
+
+#include "tmpl-part-req-1.h"
+import "tmpl-part-req-1_a.H";
+
+// { dg-final { scan-lang-dump-not {merge key \(new\)} module } }
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-2.h b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2.h
new file mode 100644
index 00000000000..0ca05933b11
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2.h
@@ -0,0 +1,24 @@
+
+template<typename _Iterator, typename>
+struct Trait;
+
+template<typename _Iterator>
+struct Trait<_Iterator, void> {};
+
+template<typename _Iterator>
+requires true && true
+struct Trait<_Iterator, void>
+{
+ template<typename _Iter> struct __cat {};
+
+ template<typename _Iter> requires true struct __cat<_Iter> {};
+};
+
+template<typename _Iterator>
+requires true
+struct Trait<_Iterator, void>
+{
+ template<typename _Iter> struct __diff {};
+
+ template<typename _Iter> requires true struct __diff<_Iter> {};
+};
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_a.H b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_a.H
new file mode 100644
index 00000000000..66b97ea372b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_a.H
@@ -0,0 +1,3 @@
+// { dg-additional-options "-fmodule-header -std=c++2a" }
+// { dg-module-cmi {} }
+#include "tmpl-part-req-2.h"
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_b.C b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_b.C
new file mode 100644
index 00000000000..1176f6012c7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_b.C
@@ -0,0 +1,6 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts -fno-module-lazy -fdump-lang-module-alias" }
+
+#include "tmpl-part-req-2.h"
+import "tmpl-part-req-2_a.H";
+
+// { dg-final { scan-lang-dump-not {merge key \(new\)} module } }
More information about the Gcc-cvs
mailing list