From: Mark Wielaard <mark@klomp.org>
To: gcc-rust@gcc.gnu.org
Cc: Mark Wielaard <mark@klomp.org>
Subject: [PATCH 2/2] WIP union hir-lowering and type support
Date: Fri, 23 Jul 2021 01:19:02 +0200 [thread overview]
Message-ID: <20210722231902.7401-3-mark@klomp.org> (raw)
In-Reply-To: <20210722231902.7401-1-mark@klomp.org>
Treat a union as a Struct variant like a tuple struct. Add an
iterator and get_identifier functions to the AST Union class. Same
for the HIR Union class, plus a get_generics_params method. Add a
get_is_union method tot the ADTType.
---
gcc/rust/ast/rust-item.h | 11 ++++
gcc/rust/hir/rust-ast-lower-item.h | 51 +++++++++++++++++
gcc/rust/hir/rust-ast-lower-stmt.h | 53 ++++++++++++++++++
gcc/rust/hir/tree/rust-hir-item.h | 16 ++++++
gcc/rust/resolve/rust-ast-resolve-item.h | 22 ++++++++
gcc/rust/resolve/rust-ast-resolve-stmt.h | 32 +++++++++++
gcc/rust/resolve/rust-ast-resolve-toplevel.h | 14 +++++
gcc/rust/typecheck/rust-hir-type-check-stmt.h | 55 ++++++++++++++++++-
.../typecheck/rust-hir-type-check-toplevel.h | 54 +++++++++++++++++-
gcc/rust/typecheck/rust-hir-type-check.cc | 12 +++-
gcc/rust/typecheck/rust-tycheck-dump.h | 6 ++
gcc/rust/typecheck/rust-tyty.cc | 4 +-
gcc/rust/typecheck/rust-tyty.h | 12 ++--
13 files changed, 331 insertions(+), 11 deletions(-)
diff --git a/gcc/rust/ast/rust-item.h b/gcc/rust/ast/rust-item.h
index 30cab0ed900..1e928e8111a 100644
--- a/gcc/rust/ast/rust-item.h
+++ b/gcc/rust/ast/rust-item.h
@@ -2489,6 +2489,15 @@ public:
std::vector<StructField> &get_variants () { return variants; }
const std::vector<StructField> &get_variants () const { return variants; }
+ void iterate (std::function<bool (StructField &)> cb)
+ {
+ for (auto &variant : variants)
+ {
+ if (!cb (variant))
+ return;
+ }
+ }
+
std::vector<std::unique_ptr<GenericParam> > &get_generic_params ()
{
return generic_params;
@@ -2505,6 +2514,8 @@ public:
return where_clause;
}
+ Identifier get_identifier () const { return union_name; }
+
protected:
/* Use covariance to implement clone function as returning this object
* rather than base */
diff --git a/gcc/rust/hir/rust-ast-lower-item.h b/gcc/rust/hir/rust-ast-lower-item.h
index 5ba59183179..b6af00f6b54 100644
--- a/gcc/rust/hir/rust-ast-lower-item.h
+++ b/gcc/rust/hir/rust-ast-lower-item.h
@@ -192,6 +192,57 @@ public:
struct_decl.get_locus ());
}
+ void visit (AST::Union &union_decl) override
+ {
+ std::vector<std::unique_ptr<HIR::GenericParam> > generic_params;
+ if (union_decl.has_generics ())
+ {
+ generic_params
+ = lower_generic_params (union_decl.get_generic_params ());
+ }
+
+ std::vector<std::unique_ptr<HIR::WhereClauseItem> > where_clause_items;
+ HIR::WhereClause where_clause (std::move (where_clause_items));
+ HIR::Visibility vis = HIR::Visibility::create_public ();
+
+ std::vector<HIR::StructField> variants;
+ union_decl.iterate ([&] (AST::StructField &variant) mutable -> bool {
+ HIR::Visibility vis = HIR::Visibility::create_public ();
+ HIR::Type *type
+ = ASTLoweringType::translate (variant.get_field_type ().get ());
+
+ auto crate_num = mappings->get_current_crate ();
+ Analysis::NodeMapping mapping (crate_num, variant.get_node_id (),
+ mappings->get_next_hir_id (crate_num),
+ mappings->get_next_localdef_id (
+ crate_num));
+
+ HIR::StructField translated_variant (mapping, variant.get_field_name (),
+ std::unique_ptr<HIR::Type> (type),
+ vis, variant.get_locus (),
+ variant.get_outer_attrs ());
+ variants.push_back (std::move (translated_variant));
+ return true;
+ });
+
+ auto crate_num = mappings->get_current_crate ();
+ Analysis::NodeMapping mapping (crate_num, union_decl.get_node_id (),
+ mappings->get_next_hir_id (crate_num),
+ mappings->get_next_localdef_id (crate_num));
+
+ translated
+ = new HIR::Union (mapping, union_decl.get_identifier (), vis,
+ std::move (generic_params), std::move (where_clause),
+ std::move (variants), union_decl.get_outer_attrs (),
+ union_decl.get_locus ());
+
+ mappings->insert_defid_mapping (mapping.get_defid (), translated);
+ mappings->insert_hir_item (mapping.get_crate_num (), mapping.get_hirid (),
+ translated);
+ mappings->insert_location (crate_num, mapping.get_hirid (),
+ union_decl.get_locus ());
+ }
+
void visit (AST::StaticItem &var) override
{
HIR::Visibility vis = HIR::Visibility::create_public ();
diff --git a/gcc/rust/hir/rust-ast-lower-stmt.h b/gcc/rust/hir/rust-ast-lower-stmt.h
index 9df6b746bb7..2e97ca63a13 100644
--- a/gcc/rust/hir/rust-ast-lower-stmt.h
+++ b/gcc/rust/hir/rust-ast-lower-stmt.h
@@ -215,6 +215,59 @@ public:
struct_decl.get_locus ());
}
+ void visit (AST::Union &union_decl) override
+ {
+ std::vector<std::unique_ptr<HIR::GenericParam> > generic_params;
+ if (union_decl.has_generics ())
+ {
+ generic_params
+ = lower_generic_params (union_decl.get_generic_params ());
+ }
+
+ std::vector<std::unique_ptr<HIR::WhereClauseItem> > where_clause_items;
+ HIR::WhereClause where_clause (std::move (where_clause_items));
+ HIR::Visibility vis = HIR::Visibility::create_public ();
+
+ std::vector<HIR::StructField> variants;
+ union_decl.iterate ([&] (AST::StructField &variant) mutable -> bool {
+ HIR::Visibility vis = HIR::Visibility::create_public ();
+ HIR::Type *type
+ = ASTLoweringType::translate (variant.get_field_type ().get ());
+
+ auto crate_num = mappings->get_current_crate ();
+ Analysis::NodeMapping mapping (crate_num, variant.get_node_id (),
+ mappings->get_next_hir_id (crate_num),
+ mappings->get_next_localdef_id (
+ crate_num));
+
+ // FIXME
+ // AST::StructField is missing Location info
+ Location variant_locus;
+ HIR::StructField translated_variant (mapping, variant.get_field_name (),
+ std::unique_ptr<HIR::Type> (type),
+ vis, variant_locus,
+ variant.get_outer_attrs ());
+ variants.push_back (std::move (translated_variant));
+ return true;
+ });
+
+ auto crate_num = mappings->get_current_crate ();
+ Analysis::NodeMapping mapping (crate_num, union_decl.get_node_id (),
+ mappings->get_next_hir_id (crate_num),
+ mappings->get_next_localdef_id (crate_num));
+
+ translated
+ = new HIR::Union (mapping, union_decl.get_identifier (), vis,
+ std::move (generic_params), std::move (where_clause),
+ std::move (variants), union_decl.get_outer_attrs (),
+ union_decl.get_locus ());
+
+ mappings->insert_hir_stmt (mapping.get_crate_num (), mapping.get_hirid (),
+ translated);
+ mappings->insert_location (crate_num, mapping.get_hirid (),
+ union_decl.get_locus ());
+ }
+
void visit (AST::EmptyStmt &empty) override
{
auto crate_num = mappings->get_current_crate ();
diff --git a/gcc/rust/hir/tree/rust-hir-item.h b/gcc/rust/hir/tree/rust-hir-item.h
index e7e110fda92..cfe45d73d85 100644
--- a/gcc/rust/hir/tree/rust-hir-item.h
+++ b/gcc/rust/hir/tree/rust-hir-item.h
@@ -1989,10 +1989,26 @@ public:
Union (Union &&other) = default;
Union &operator= (Union &&other) = default;
+ std::vector<std::unique_ptr<GenericParam> > &get_generic_params ()
+ {
+ return generic_params;
+ }
+
+ Identifier get_identifier () const { return union_name; }
+
Location get_locus () const { return locus; }
void accept_vis (HIRVisitor &vis) override;
+ void iterate (std::function<bool (StructField &)> cb)
+ {
+ for (auto &variant : variants)
+ {
+ if (!cb (variant))
+ return;
+ }
+ }
+
protected:
/* Use covariance to implement clone function as returning this object
* rather than base */
diff --git a/gcc/rust/resolve/rust-ast-resolve-item.h b/gcc/rust/resolve/rust-ast-resolve-item.h
index 0714f5d5706..54f1fe15533 100644
--- a/gcc/rust/resolve/rust-ast-resolve-item.h
+++ b/gcc/rust/resolve/rust-ast-resolve-item.h
@@ -260,6 +260,28 @@ public:
resolver->get_type_scope ().pop ();
}
+ void visit (AST::Union &union_decl) override
+ {
+ NodeId scope_node_id = union_decl.get_node_id ();
+ resolver->get_type_scope ().push (scope_node_id);
+
+ if (union_decl.has_generics ())
+ {
+ for (auto &generic : union_decl.get_generic_params ())
+ {
+ ResolveGenericParam::go (generic.get (), union_decl.get_node_id ());
+ }
+ }
+
+ union_decl.iterate ([&] (AST::StructField &field) mutable -> bool {
+ ResolveType::go (field.get_field_type ().get (),
+ union_decl.get_node_id ());
+ return true;
+ });
+
+ resolver->get_type_scope ().pop ();
+ }
+
void visit (AST::StaticItem &var) override
{
ResolveType::go (var.get_type ().get (), var.get_node_id ());
diff --git a/gcc/rust/resolve/rust-ast-resolve-stmt.h b/gcc/rust/resolve/rust-ast-resolve-stmt.h
index 210a9fc047d..b6044327b27 100644
--- a/gcc/rust/resolve/rust-ast-resolve-stmt.h
+++ b/gcc/rust/resolve/rust-ast-resolve-stmt.h
@@ -131,6 +131,38 @@ public:
resolver->get_type_scope ().pop ();
}
+ void visit (AST::Union &union_decl) override
+ {
+ auto path = CanonicalPath::new_seg (union_decl.get_node_id (),
+ union_decl.get_identifier ());
+ resolver->get_type_scope ().insert (
+ path, union_decl.get_node_id (), union_decl.get_locus (), false,
+ [&] (const CanonicalPath &, NodeId, Location locus) -> void {
+ RichLocation r (union_decl.get_locus ());
+ r.add_range (locus);
+ rust_error_at (r, "redefined multiple times");
+ });
+
+ NodeId scope_node_id = union_decl.get_node_id ();
+ resolver->get_type_scope ().push (scope_node_id);
+
+ if (union_decl.has_generics ())
+ {
+ for (auto &generic : union_decl.get_generic_params ())
+ {
+ ResolveGenericParam::go (generic.get (), union_decl.get_node_id ());
+ }
+ }
+
+ union_decl.iterate ([&] (AST::StructField &field) mutable -> bool {
+ ResolveType::go (field.get_field_type ().get (),
+ union_decl.get_node_id ());
+ return true;
+ });
+
+ resolver->get_type_scope ().pop ();
+ }
+
void visit (AST::Function &function) override
{
auto path = ResolveFunctionItemToCanonicalPath::resolve (function);
diff --git a/gcc/rust/resolve/rust-ast-resolve-toplevel.h b/gcc/rust/resolve/rust-ast-resolve-toplevel.h
index 9abbb18e080..4df0467b994 100644
--- a/gcc/rust/resolve/rust-ast-resolve-toplevel.h
+++ b/gcc/rust/resolve/rust-ast-resolve-toplevel.h
@@ -81,6 +81,20 @@ public:
});
}
+ void visit (AST::Union &union_decl) override
+ {
+ auto path
+ = prefix.append (CanonicalPath::new_seg (union_decl.get_node_id (),
+ union_decl.get_identifier ()));
+ resolver->get_type_scope ().insert (
+ path, union_decl.get_node_id (), union_decl.get_locus (), false,
+ [&] (const CanonicalPath &, NodeId, Location locus) -> void {
+ RichLocation r (union_decl.get_locus ());
+ r.add_range (locus);
+ rust_error_at (r, "redefined multiple times");
+ });
+ }
+
void visit (AST::StaticItem &var) override
{
auto path = prefix.append (
diff --git a/gcc/rust/typecheck/rust-hir-type-check-stmt.h b/gcc/rust/typecheck/rust-hir-type-check-stmt.h
index 1b6f47c1595..fad2b7183df 100644
--- a/gcc/rust/typecheck/rust-hir-type-check-stmt.h
+++ b/gcc/rust/typecheck/rust-hir-type-check-stmt.h
@@ -159,7 +159,7 @@ public:
TyTy::BaseType *type
= new TyTy::ADTType (struct_decl.get_mappings ().get_hirid (),
mappings->get_next_hir_id (),
- struct_decl.get_identifier (), true,
+ struct_decl.get_identifier (), true, false,
std::move (fields), std::move (substitutions));
context->insert_type (struct_decl.get_mappings (), type);
@@ -209,13 +209,64 @@ public:
TyTy::BaseType *type
= new TyTy::ADTType (struct_decl.get_mappings ().get_hirid (),
mappings->get_next_hir_id (),
- struct_decl.get_identifier (), false,
+ struct_decl.get_identifier (), false, false,
std::move (fields), std::move (substitutions));
context->insert_type (struct_decl.get_mappings (), type);
infered = type;
}
+ void visit (HIR::Union &union_decl) override
+ {
+ std::vector<TyTy::SubstitutionParamMapping> substitutions;
+ if (union_decl.has_generics ())
+ {
+ for (auto &generic_param : union_decl.get_generic_params ())
+ {
+ switch (generic_param.get ()->get_kind ())
+ {
+ case HIR::GenericParam::GenericKind::LIFETIME:
+ // Skipping Lifetime completely until better handling.
+ break;
+
+ case HIR::GenericParam::GenericKind::TYPE: {
+ auto param_type
+ = TypeResolveGenericParam::Resolve (generic_param.get ());
+ context->insert_type (generic_param->get_mappings (),
+ param_type);
+
+ substitutions.push_back (TyTy::SubstitutionParamMapping (
+ static_cast<HIR::TypeParam &> (*generic_param),
+ param_type));
+ }
+ break;
+ }
+ }
+ }
+
+ std::vector<TyTy::StructFieldType *> variants;
+ union_decl.iterate ([&] (HIR::StructField &variant) mutable -> bool {
+ TyTy::BaseType *variant_type
+ = TypeCheckType::Resolve (variant.get_field_type ().get ());
+ TyTy::StructFieldType *ty_variant
+ = new TyTy::StructFieldType (variant.get_mappings ().get_hirid (),
+ variant.get_field_name (), variant_type);
+ variants.push_back (ty_variant);
+ context->insert_type (variant.get_mappings (),
+ ty_variant->get_field_type ());
+ return true;
+ });
+
+ TyTy::BaseType *type
+ = new TyTy::ADTType (union_decl.get_mappings ().get_hirid (),
+ mappings->get_next_hir_id (),
+ union_decl.get_identifier (), false, true,
+ std::move (variants), std::move (substitutions));
+
+ context->insert_type (union_decl.get_mappings (), type);
+ infered = type;
+ }
+
void visit (HIR::Function &function) override
{
std::vector<TyTy::SubstitutionParamMapping> substitutions;
diff --git a/gcc/rust/typecheck/rust-hir-type-check-toplevel.h b/gcc/rust/typecheck/rust-hir-type-check-toplevel.h
index dd3dd751ad6..a723e7e679f 100644
--- a/gcc/rust/typecheck/rust-hir-type-check-toplevel.h
+++ b/gcc/rust/typecheck/rust-hir-type-check-toplevel.h
@@ -94,7 +94,7 @@ public:
TyTy::BaseType *type
= new TyTy::ADTType (struct_decl.get_mappings ().get_hirid (),
mappings->get_next_hir_id (),
- struct_decl.get_identifier (), true,
+ struct_decl.get_identifier (), true, false,
std::move (fields), std::move (substitutions));
context->insert_type (struct_decl.get_mappings (), type);
@@ -143,12 +143,62 @@ public:
TyTy::BaseType *type
= new TyTy::ADTType (struct_decl.get_mappings ().get_hirid (),
mappings->get_next_hir_id (),
- struct_decl.get_identifier (), false,
+ struct_decl.get_identifier (), false, false,
std::move (fields), std::move (substitutions));
context->insert_type (struct_decl.get_mappings (), type);
}
+ void visit (HIR::Union &union_decl) override
+ {
+ std::vector<TyTy::SubstitutionParamMapping> substitutions;
+ if (union_decl.has_generics ())
+ {
+ for (auto &generic_param : union_decl.get_generic_params ())
+ {
+ switch (generic_param.get ()->get_kind ())
+ {
+ case HIR::GenericParam::GenericKind::LIFETIME:
+ // Skipping Lifetime completely until better handling.
+ break;
+
+ case HIR::GenericParam::GenericKind::TYPE: {
+ auto param_type
+ = TypeResolveGenericParam::Resolve (generic_param.get ());
+ context->insert_type (generic_param->get_mappings (),
+ param_type);
+
+ substitutions.push_back (TyTy::SubstitutionParamMapping (
+ static_cast<HIR::TypeParam &> (*generic_param),
+ param_type));
+ }
+ break;
+ }
+ }
+ }
+
+ std::vector<TyTy::StructFieldType *> variants;
+ union_decl.iterate ([&] (HIR::StructField &variant) mutable -> bool {
+ TyTy::BaseType *variant_type
+ = TypeCheckType::Resolve (variant.get_field_type ().get ());
+ TyTy::StructFieldType *ty_variant
+ = new TyTy::StructFieldType (variant.get_mappings ().get_hirid (),
+ variant.get_field_name (), variant_type);
+ variants.push_back (ty_variant);
+ context->insert_type (variant.get_mappings (),
+ ty_variant->get_field_type ());
+ return true;
+ });
+
+ TyTy::BaseType *type
+ = new TyTy::ADTType (union_decl.get_mappings ().get_hirid (),
+ mappings->get_next_hir_id (),
+ union_decl.get_identifier (), false, true,
+ std::move (variants), std::move (substitutions));
+
+ context->insert_type (union_decl.get_mappings (), type);
+ }
+
void visit (HIR::StaticItem &var) override
{
TyTy::BaseType *type = TypeCheckType::Resolve (var.get_type ());
diff --git a/gcc/rust/typecheck/rust-hir-type-check.cc b/gcc/rust/typecheck/rust-hir-type-check.cc
index cb2896c0bb4..da528d7878a 100644
--- a/gcc/rust/typecheck/rust-hir-type-check.cc
+++ b/gcc/rust/typecheck/rust-hir-type-check.cc
@@ -180,7 +180,17 @@ TypeCheckStructExpr::visit (HIR::StructExprStructFields &struct_expr)
// check the arguments are all assigned and fix up the ordering
if (fields_assigned.size () != struct_path_resolved->num_fields ())
{
- if (!struct_expr.has_struct_base ())
+ if (struct_def->get_is_union ())
+ {
+ if (fields_assigned.size () != 1)
+ {
+ rust_error_at (
+ struct_expr.get_locus (),
+ "union must have exactly one field variant assigned");
+ return;
+ }
+ }
+ else if (!struct_expr.has_struct_base ())
{
rust_error_at (struct_expr.get_locus (),
"constructor is missing fields");
diff --git a/gcc/rust/typecheck/rust-tycheck-dump.h b/gcc/rust/typecheck/rust-tycheck-dump.h
index b80372b2a9c..cc2e3c01110 100644
--- a/gcc/rust/typecheck/rust-tycheck-dump.h
+++ b/gcc/rust/typecheck/rust-tycheck-dump.h
@@ -48,6 +48,12 @@ public:
+ "\n";
}
+ void visit (HIR::Union &union_decl) override
+ {
+ dump
+ += indent () + "union " + type_string (union_decl.get_mappings ()) + "\n";
+ }
+
void visit (HIR::ImplBlock &impl_block) override
{
dump += indent () + "impl "
diff --git a/gcc/rust/typecheck/rust-tyty.cc b/gcc/rust/typecheck/rust-tyty.cc
index f043c7eabda..d059134f8a0 100644
--- a/gcc/rust/typecheck/rust-tyty.cc
+++ b/gcc/rust/typecheck/rust-tyty.cc
@@ -517,8 +517,8 @@ ADTType::clone ()
cloned_fields.push_back ((StructFieldType *) f->clone ());
return new ADTType (get_ref (), get_ty_ref (), identifier, get_is_tuple (),
- cloned_fields, clone_substs (), used_arguments,
- get_combined_refs ());
+ get_is_union (), cloned_fields, clone_substs (),
+ used_arguments, get_combined_refs ());
}
ADTType *
diff --git a/gcc/rust/typecheck/rust-tyty.h b/gcc/rust/typecheck/rust-tyty.h
index 2152c1b6d76..b7cf46bb783 100644
--- a/gcc/rust/typecheck/rust-tyty.h
+++ b/gcc/rust/typecheck/rust-tyty.h
@@ -848,7 +848,7 @@ protected:
class ADTType : public BaseType, public SubstitutionRef
{
public:
- ADTType (HirId ref, std::string identifier, bool is_tuple,
+ ADTType (HirId ref, std::string identifier, bool is_tuple, bool is_union,
std::vector<StructFieldType *> fields,
std::vector<SubstitutionParamMapping> subst_refs,
SubstitutionArgumentMappings generic_arguments
@@ -856,21 +856,24 @@ public:
std::set<HirId> refs = std::set<HirId> ())
: BaseType (ref, ref, TypeKind::ADT, refs),
SubstitutionRef (std::move (subst_refs), std::move (generic_arguments)),
- identifier (identifier), fields (fields), is_tuple (is_tuple)
+ identifier (identifier), fields (fields), is_tuple (is_tuple),
+ is_union (is_union)
{}
ADTType (HirId ref, HirId ty_ref, std::string identifier, bool is_tuple,
- std::vector<StructFieldType *> fields,
+ bool is_union, std::vector<StructFieldType *> fields,
std::vector<SubstitutionParamMapping> subst_refs,
SubstitutionArgumentMappings generic_arguments
= SubstitutionArgumentMappings::error (),
std::set<HirId> refs = std::set<HirId> ())
: BaseType (ref, ty_ref, TypeKind::ADT, refs),
SubstitutionRef (std::move (subst_refs), std::move (generic_arguments)),
- identifier (identifier), fields (fields), is_tuple (is_tuple)
+ identifier (identifier), fields (fields), is_tuple (is_tuple),
+ is_union (is_union)
{}
bool get_is_tuple () { return is_tuple; }
+ bool get_is_union () { return is_union; }
bool is_unit () const override { return this->fields.empty (); }
@@ -957,6 +960,7 @@ private:
std::string identifier;
std::vector<StructFieldType *> fields;
bool is_tuple;
+ bool is_union;
};
class FnType : public BaseType, public SubstitutionRef
--
2.32.0
next prev parent reply other threads:[~2021-07-22 23:19 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-07-22 23:19 union support Mark Wielaard
2021-07-22 23:19 ` [PATCH 1/2] Better union support in the parser Mark Wielaard
2021-07-23 11:19 ` Philip Herron
2021-07-22 23:19 ` Mark Wielaard [this message]
2021-07-23 11:19 ` [PATCH 2/2] WIP union hir-lowering and type support Philip Herron
2021-08-01 11:29 ` Mark Wielaard
2021-08-01 22:37 ` Mark Wielaard
2021-08-02 12:33 ` Thomas Schwinge
2021-08-04 21:04 ` Mark Wielaard
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210722231902.7401-3-mark@klomp.org \
--to=mark@klomp.org \
--cc=gcc-rust@gcc.gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).