]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
gccrs: expand: Add handling for macro expansion in pattern context
authorArthur Cohen <arthur.cohen@embecosm.com>
Wed, 13 Aug 2025 09:39:58 +0000 (11:39 +0200)
committerArthur Cohen <arthur.cohen@embecosm.com>
Thu, 30 Oct 2025 19:58:38 +0000 (20:58 +0100)
gcc/rust/ChangeLog:

* ast/rust-ast-fragment.cc (Fragment::is_pattern_fragment): New function.
(Fragment::take_pattern_fragment): Likewise.
(Fragment::assert_single_fragment): Likewise.
* ast/rust-ast-fragment.h: Declare them.
* ast/rust-ast.cc (SingleASTNode::SingleASTNode): Add new constructor for pattern
single AST nodes.
(SingleASTNode::operator=): Handle patterns.
(SingleASTNode::accept_vis): Likewise.
(SingleASTNode::is_error): Likewise.
(SingleASTNode::as_string): Likewise.
* ast/rust-ast.h: Add get_pattern_ptr() functions.
* ast/rust-expr.h: Likewise.
* ast/rust-item.h: Likewise.
* ast/rust-pattern.h: Likewise.
* ast/rust-stmt.h: Likewise.
* expand/rust-expand-visitor.cc (derive_item): Use new API enum values.
(expand_item_attribute): Likewise.
(expand_stmt_attribute): Likewise.
(ExpandVisitor::maybe_expand_pattern): New function.
(ExpandVisitor::expand_closure_params): Handle patterns.
(ExpandVisitor::visit): Add new visitors for patterns and missed exprs.
* expand/rust-expand-visitor.h: Declare them.
* expand/rust-macro-expand.cc (transcribe_pattern): New function.
(transcribe_context): Call it.
* expand/rust-macro-expand.h (struct MacroExpander): New Context type.

gcc/testsuite/ChangeLog:

* rust/compile/issue-3726.rs: New test.
* rust/compile/issue-3898.rs: New test.

14 files changed:
gcc/rust/ast/rust-ast-fragment.cc
gcc/rust/ast/rust-ast-fragment.h
gcc/rust/ast/rust-ast.cc
gcc/rust/ast/rust-ast.h
gcc/rust/ast/rust-expr.h
gcc/rust/ast/rust-item.h
gcc/rust/ast/rust-pattern.h
gcc/rust/ast/rust-stmt.h
gcc/rust/expand/rust-expand-visitor.cc
gcc/rust/expand/rust-expand-visitor.h
gcc/rust/expand/rust-macro-expand.cc
gcc/rust/expand/rust-macro-expand.h
gcc/testsuite/rust/compile/issue-3726.rs [new file with mode: 0644]
gcc/testsuite/rust/compile/issue-3898.rs [new file with mode: 0644]

index 056fcac55b0b630902c2cfba47b9f4f032ae61e4..8a547b4bcb552e13ac707e2814b005544d55f51c 100644 (file)
@@ -116,6 +116,12 @@ Fragment::is_type_fragment () const
   return is_single_fragment_of_kind (SingleASTNode::Kind::Type);
 }
 
+bool
+Fragment::is_pattern_fragment () const
+{
+  return is_single_fragment_of_kind (SingleASTNode::Kind::Pattern);
+}
+
 std::unique_ptr<Expr>
 Fragment::take_expression_fragment ()
 {
@@ -130,6 +136,13 @@ Fragment::take_type_fragment ()
   return nodes[0].take_type ();
 }
 
+std::unique_ptr<Pattern>
+Fragment::take_pattern_fragment ()
+{
+  assert_single_fragment (SingleASTNode::Kind::Pattern);
+  return nodes[0].take_pattern ();
+}
+
 void
 Fragment::accept_vis (ASTVisitor &vis)
 {
@@ -159,6 +172,7 @@ Fragment::assert_single_fragment (SingleASTNode::Kind expected) const
     {SingleASTNode::Kind::Expr, "expr"},
     {SingleASTNode::Kind::Stmt, "stmt"},
     {SingleASTNode::Kind::Extern, "extern"},
+    {SingleASTNode::Kind::Pattern, "pattern"},
   };
 
   auto actual = nodes[0].get_kind ();
index d7584d0697c21572b7fbff144aa938909b48e692..23f26d3e61692ae17ec1f1116680af63fa6425af 100644 (file)
@@ -86,9 +86,11 @@ public:
 
   bool is_expression_fragment () const;
   bool is_type_fragment () const;
+  bool is_pattern_fragment () const;
 
   std::unique_ptr<Expr> take_expression_fragment ();
   std::unique_ptr<Type> take_type_fragment ();
+  std::unique_ptr<Pattern> take_pattern_fragment ();
 
   void accept_vis (ASTVisitor &vis);
 
index c9b6fcef9d6c7531b7f367172697fcba22a131fd..7feb7a688f7df3fff7f404db381819c18b6347d6 100644 (file)
@@ -71,6 +71,10 @@ SingleASTNode::SingleASTNode (SingleASTNode const &other)
     case Kind::Type:
       type = other.type->clone_type ();
       break;
+
+    case Kind::Pattern:
+      pattern = other.pattern->clone_pattern ();
+      break;
     }
 }
 
@@ -103,6 +107,10 @@ SingleASTNode::operator= (SingleASTNode const &other)
     case Kind::Type:
       type = other.type->clone_type ();
       break;
+
+    case Kind::Pattern:
+      pattern = other.pattern->clone_pattern ();
+      break;
     }
   return *this;
 }
@@ -135,6 +143,10 @@ SingleASTNode::accept_vis (ASTVisitor &vis)
     case Kind::Type:
       type->accept_vis (vis);
       break;
+
+    case Kind::Pattern:
+      pattern->accept_vis (vis);
+      break;
     }
 }
 
@@ -155,6 +167,8 @@ SingleASTNode::is_error ()
       return assoc_item == nullptr;
     case Kind::Type:
       return type == nullptr;
+    case Kind::Pattern:
+      return pattern == nullptr;
     }
 
   rust_unreachable ();
@@ -178,6 +192,8 @@ SingleASTNode::as_string () const
       return "Associated Item: " + assoc_item->as_string ();
     case Kind::Type:
       return "Type: " + type->as_string ();
+    case Kind::Pattern:
+      return "Pattern: " + pattern->as_string ();
     }
 
   rust_unreachable ();
index 148e297c543422f02b81ac4bcc86c0f11892dd0a..a7e62965201fe3dcc40eaf0ce8d9e85131d6c725 100644 (file)
@@ -1970,6 +1970,7 @@ public:
     Extern,
     Assoc,
     Type,
+    Pattern,
   };
 
 private:
@@ -1982,6 +1983,7 @@ private:
   std::unique_ptr<ExternalItem> external_item;
   std::unique_ptr<AssociatedItem> assoc_item;
   std::unique_ptr<Type> type;
+  std::unique_ptr<Pattern> pattern;
 
 public:
   SingleASTNode (std::unique_ptr<Expr> expr)
@@ -2008,6 +2010,10 @@ public:
     : kind (Kind::Type), type (std::move (type))
   {}
 
+  SingleASTNode (std::unique_ptr<Pattern> pattern)
+    : kind (Kind::Pattern), pattern (std::move (pattern))
+  {}
+
   SingleASTNode (SingleASTNode const &other);
 
   SingleASTNode operator= (SingleASTNode const &other);
@@ -2076,6 +2082,12 @@ public:
     return std::move (type);
   }
 
+  std::unique_ptr<Pattern> take_pattern ()
+  {
+    rust_assert (!is_error ());
+    return std::move (pattern);
+  }
+
   void accept_vis (ASTVisitor &vis) override;
 
   bool is_error ();
index ee3919a9a9102158581cd718740e51d8edea3230..94d9ba13f1dfcd0b88f8cfb149a6cc5fd8a03003 100644 (file)
@@ -2492,6 +2492,12 @@ public:
     return *pattern;
   }
 
+  std::unique_ptr<Pattern> &get_pattern_ptr ()
+  {
+    rust_assert (pattern != nullptr);
+    return pattern;
+  }
+
   Type &get_type ()
   {
     rust_assert (has_type_given ());
index 375f66ddcc1e8081fcf819edd27c8b8928964bb3..3fd49f4b199dcb97a668668618d6c8ebbd35a12d 100644 (file)
@@ -631,6 +631,12 @@ public:
     return *param_name;
   }
 
+  std::unique_ptr<Pattern> &get_pattern_ptr ()
+  {
+    rust_assert (param_name != nullptr);
+    return param_name;
+  }
+
   const Pattern &get_pattern () const
   {
     rust_assert (param_name != nullptr);
@@ -714,6 +720,12 @@ public:
     return *param_name;
   }
 
+  std::unique_ptr<Pattern> &get_pattern_ptr ()
+  {
+    rust_assert (param_name != nullptr);
+    return param_name;
+  }
+
   bool has_name () const { return param_name != nullptr; }
 
   // TODO: is this better? Or is a "vis_block" better?
index 4948159f5376bbd934701875027266dc0e3d6091..029a5b36c6a780f9067b77c9dff264a9f540f147 100644 (file)
@@ -1509,6 +1509,12 @@ public:
     return *pattern_in_parens;
   }
 
+  std::unique_ptr<Pattern> &get_pattern_in_parens_ptr ()
+  {
+    rust_assert (pattern_in_parens != nullptr);
+    return pattern_in_parens;
+  }
+
   NodeId get_node_id () const override { return node_id; }
 
   Pattern::Kind get_pattern_kind () override { return Pattern::Kind::Grouped; }
index f843a79b3f99a9b7ecc5a7df657f1ee0a48bb778..5fb00ef18057f902832f4cfafe54193af2f3c0c9 100644 (file)
@@ -201,6 +201,12 @@ public:
     return *variables_pattern;
   }
 
+  std::unique_ptr<Pattern> &get_pattern_ptr ()
+  {
+    rust_assert (variables_pattern != nullptr);
+    return variables_pattern;
+  }
+
   Type &get_type ()
   {
     rust_assert (has_type ());
index a53f0640109d8b684148762492112cc5a8432e4a..c1833c276c5a30bebd9fbce986ef15cc5bc8c945 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "rust-expand-visitor.h"
 #include "rust-ast-fragment.h"
+#include "rust-item.h"
 #include "rust-proc-macro.h"
 #include "rust-attributes.h"
 #include "rust-ast.h"
@@ -62,7 +63,7 @@ derive_item (AST::Item &item, AST::SimplePath &to_derive,
        {
          switch (node.get_kind ())
            {
-           case AST::SingleASTNode::ITEM:
+           case AST::SingleASTNode::Kind::Item:
              result.push_back (node.take_item ());
              break;
            default:
@@ -85,7 +86,7 @@ expand_item_attribute (AST::Item &item, AST::SimplePath &name,
        {
          switch (node.get_kind ())
            {
-           case AST::SingleASTNode::ITEM:
+           case AST::SingleASTNode::Kind::Item:
              result.push_back (node.take_item ());
              break;
            default:
@@ -114,7 +115,7 @@ expand_stmt_attribute (T &statement, AST::SimplePath &attribute,
        {
          switch (node.get_kind ())
            {
-           case AST::SingleASTNode::STMT:
+           case AST::SingleASTNode::Kind::Stmt:
              result.push_back (node.take_stmt ());
              break;
            default:
@@ -380,6 +381,23 @@ ExpandVisitor::maybe_expand_type (std::unique_ptr<AST::TypeNoBounds> &type)
       final_fragment.take_type_fragment (), BUILTINS_LOCATION);
 }
 
+void
+ExpandVisitor::maybe_expand_pattern (std::unique_ptr<AST::Pattern> &pattern)
+{
+  NodeId old_expect = pattern->get_node_id ();
+  std::swap (macro_invoc_expect_id, old_expect);
+
+  expander.push_context (MacroExpander::ContextType::PATTERN);
+  pattern->accept_vis (*this);
+  expander.pop_context ();
+
+  std::swap (macro_invoc_expect_id, old_expect);
+
+  auto final_fragment = expander.take_expanded_fragment ();
+  if (final_fragment.should_expand () && final_fragment.is_pattern_fragment ())
+    pattern = final_fragment.take_pattern_fragment ();
+}
+
 // FIXME: Can this be refactored into a `scoped` method? Which takes a
 // ContextType as parameter and a lambda? And maybe just an std::vector<T>&?
 void
@@ -452,6 +470,8 @@ ExpandVisitor::expand_closure_params (std::vector<AST::ClosureParam> &params)
 {
   for (auto &param : params)
     {
+      maybe_expand_pattern (param.get_pattern_ptr ());
+
       if (param.has_type_given ())
        maybe_expand_type (param.get_type_ptr ());
     }
@@ -729,7 +749,7 @@ ExpandVisitor::visit (AST::MatchExpr &expr)
       auto &arm = match_case.get_arm ();
 
       for (auto &pattern : arm.get_patterns ())
-       visit (pattern);
+       maybe_expand_pattern (pattern);
 
       if (arm.has_match_arm_guard ())
        maybe_expand_expr (arm.get_guard_expr_ptr ());
@@ -738,6 +758,13 @@ ExpandVisitor::visit (AST::MatchExpr &expr)
     }
 }
 
+void
+ExpandVisitor::visit (AST::TupleExpr &expr)
+{
+  for (auto &sub : expr.get_tuple_elems ())
+    maybe_expand_expr (sub);
+}
+
 void
 ExpandVisitor::visit (AST::TypeParam &param)
 {
@@ -1013,13 +1040,70 @@ ExpandVisitor::visit (AST::StructPatternFieldIdent &field)
 void
 ExpandVisitor::visit (AST::GroupedPattern &pattern)
 {
-  visit (pattern.get_pattern_in_parens ());
+  maybe_expand_pattern (pattern.get_pattern_in_parens_ptr ());
+}
+
+void
+ExpandVisitor::visit (AST::SlicePatternItemsNoRest &items)
+{
+  for (auto &sub : items.get_patterns ())
+    maybe_expand_pattern (sub);
+}
+
+void
+ExpandVisitor::visit (AST::SlicePatternItemsHasRest &items)
+{
+  for (auto &sub : items.get_lower_patterns ())
+    maybe_expand_pattern (sub);
+  for (auto &sub : items.get_upper_patterns ())
+    maybe_expand_pattern (sub);
+}
+
+void
+ExpandVisitor::visit (AST::AltPattern &pattern)
+{
+  for (auto &alt : pattern.get_alts ())
+    maybe_expand_pattern (alt);
+}
+
+void
+ExpandVisitor::visit (AST::TupleStructItemsNoRange &tuple_items)
+{
+  for (auto &sub : tuple_items.get_patterns ())
+    maybe_expand_pattern (sub);
+}
+
+void
+ExpandVisitor::visit (AST::TupleStructItemsRange &tuple_items)
+{
+  for (auto &sub : tuple_items.get_lower_patterns ())
+    maybe_expand_pattern (sub);
+
+  for (auto &sub : tuple_items.get_upper_patterns ())
+    maybe_expand_pattern (sub);
+}
+
+void
+ExpandVisitor::visit (AST::TuplePatternItemsMultiple &tuple_items)
+{
+  for (auto &sub : tuple_items.get_patterns ())
+    maybe_expand_pattern (sub);
+}
+
+void
+ExpandVisitor::visit (AST::TuplePatternItemsRanged &tuple_items)
+{
+  for (auto &sub : tuple_items.get_lower_patterns ())
+    maybe_expand_pattern (sub);
+
+  for (auto &sub : tuple_items.get_upper_patterns ())
+    maybe_expand_pattern (sub);
 }
 
 void
 ExpandVisitor::visit (AST::LetStmt &stmt)
 {
-  visit (stmt.get_pattern ());
+  maybe_expand_pattern (stmt.get_pattern_ptr ());
 
   if (stmt.has_type ())
     maybe_expand_type (stmt.get_type_ptr ());
@@ -1049,9 +1133,17 @@ ExpandVisitor::visit (AST::BareFunctionType &type)
 void
 ExpandVisitor::visit (AST::FunctionParam &param)
 {
+  maybe_expand_pattern (param.get_pattern_ptr ());
   maybe_expand_type (param.get_type_ptr ());
 }
 
+void
+ExpandVisitor::visit (AST::VariadicParam &param)
+{
+  if (param.has_pattern ())
+    maybe_expand_pattern (param.get_pattern_ptr ());
+}
+
 void
 ExpandVisitor::visit (AST::SelfParam &param)
 {
index 01a0d1cb2975c471f9189a17cda0426857fc205d..8fee291d595990ab3496bbe18b41a5f2a31edc1f 100644 (file)
@@ -20,6 +20,7 @@
 #define RUST_EXPAND_VISITOR_H
 
 #include "rust-ast-visitor.h"
+#include "rust-item.h"
 #include "rust-macro-expand.h"
 #include "rust-proc-macro.h"
 
@@ -47,18 +48,15 @@ public:
 
   using AST::DefaultASTVisitor::visit;
 
-  /*
-     Maybe expand a macro invocation in lieu of an expression
-     expr : Core guidelines R33, this function reseat the pointer.
-  */
-  void maybe_expand_expr (std::unique_ptr<AST::Expr> &expr);
-
-  /*
-     Maybe expand a macro invocation in lieu of a type
-     type : Core guidelines R33, this function reseat the pointer.
+  /**
+   * Maybe expand a macro invocation in lieu of an expression, type or pattern.
+   *
+   * @ptr Core guidelines R33, this function reseats the pointer.
    */
-  void maybe_expand_type (std::unique_ptr<AST::Type> &type);
+  void maybe_expand_expr (std::unique_ptr<AST::Expr> &ptr);
+  void maybe_expand_type (std::unique_ptr<AST::Type> &ptr);
   void maybe_expand_type (std::unique_ptr<AST::TypeNoBounds> &type);
+  void maybe_expand_pattern (std::unique_ptr<AST::Pattern> &ptr);
 
   /**
    * Expand all macro invocations in lieu of types within a vector of struct
@@ -239,6 +237,7 @@ public:
   void visit (AST::IfLetExpr &expr) override;
   void visit (AST::IfLetExprConseqElse &expr) override;
   void visit (AST::MatchExpr &expr) override;
+  void visit (AST::TupleExpr &expr) override;
   void visit (AST::TypeParam &param) override;
   void visit (AST::LifetimeWhereClauseItem &) override;
   void visit (AST::TypeBoundWhereClauseItem &item) override;
@@ -276,12 +275,20 @@ public:
   void visit (AST::MetaListNameValueStr &) override;
   void visit (AST::StructPatternFieldIdent &field) override;
   void visit (AST::GroupedPattern &pattern) override;
+  void visit (AST::SlicePatternItemsNoRest &items) override;
+  void visit (AST::SlicePatternItemsHasRest &items) override;
+  void visit (AST::AltPattern &pattern) override;
+  void visit (AST::TupleStructItemsNoRange &tuple_items) override;
+  void visit (AST::TupleStructItemsRange &tuple_items) override;
+  void visit (AST::TuplePatternItemsMultiple &tuple_items) override;
+  void visit (AST::TuplePatternItemsRanged &tuple_items) override;
 
   void visit (AST::LetStmt &stmt) override;
   void visit (AST::ExprStmt &stmt) override;
 
   void visit (AST::BareFunctionType &type) override;
-  void visit (AST::FunctionParam &type) override;
+  void visit (AST::FunctionParam &param) override;
+  void visit (AST::VariadicParam &param) override;
   void visit (AST::SelfParam &type) override;
 
   template <typename T>
index dfead3acc1d6fd5568e363728f9c45436aaf41cf..edaf8910d0632e22f46a3a6102e9d5919d7f2183 100644 (file)
@@ -1000,6 +1000,27 @@ transcribe_type (Parser<MacroInvocLexer> &parser)
   return AST::Fragment ({std::move (type)}, lexer.get_token_slice (start, end));
 }
 
+/**
+ * Transcribe one pattern from a macro invocation
+ *
+ * @param parser Parser to extract statements from
+ */
+static AST::Fragment
+transcribe_pattern (Parser<MacroInvocLexer> &parser)
+{
+  auto &lexer = parser.get_token_source ();
+  auto start = lexer.get_offs ();
+
+  auto pattern = parser.parse_pattern ();
+  for (auto err : parser.get_errors ())
+    err.emit ();
+
+  auto end = lexer.get_offs ();
+
+  return AST::Fragment ({std::move (pattern)},
+                       lexer.get_token_slice (start, end));
+}
+
 static AST::Fragment
 transcribe_context (MacroExpander::ContextType ctx,
                    Parser<MacroInvocLexer> &parser, bool semicolon,
@@ -1012,6 +1033,7 @@ transcribe_context (MacroExpander::ContextType ctx,
   //     -- Trait --> parser.parse_trait_item();
   //     -- Impl --> parser.parse_impl_item();
   //     -- Extern --> parser.parse_extern_item();
+  //     -- Pattern --> parser.parse_pattern();
   //     -- None --> [has semicolon?]
   //                 -- Yes --> parser.parse_stmt();
   //                 -- No --> [switch invocation.delimiter()]
@@ -1040,6 +1062,8 @@ transcribe_context (MacroExpander::ContextType ctx,
       break;
     case MacroExpander::ContextType::TYPE:
       return transcribe_type (parser);
+    case MacroExpander::ContextType::PATTERN:
+      return transcribe_pattern (parser);
       break;
     case MacroExpander::ContextType::STMT:
       return transcribe_many_stmts (parser, last_token_id, semicolon);
index 360294c6bf9f1fd8ed0684f62c40b26015092b74..901583f5d8a2610f0949210a380f1d7a9630dc72 100644 (file)
@@ -291,6 +291,7 @@ struct MacroExpander
     TRAIT,
     IMPL,
     TRAIT_IMPL,
+    PATTERN,
   };
 
   ExpansionCfg cfg;
diff --git a/gcc/testsuite/rust/compile/issue-3726.rs b/gcc/testsuite/rust/compile/issue-3726.rs
new file mode 100644 (file)
index 0000000..ced87a5
--- /dev/null
@@ -0,0 +1,17 @@
+pub enum TypeCtor {
+    Slice,
+    Array,
+}
+pub struct ApplicationTy(TypeCtor);
+
+macro_rules! ty_app {
+    ($ctor:pat) => {
+        ApplicationTy($ctor)
+    };
+}
+
+pub fn foo(ty: ApplicationTy) {
+    match ty {
+        ty_app!(TypeCtor::Array) => {}
+    }
+}
diff --git a/gcc/testsuite/rust/compile/issue-3898.rs b/gcc/testsuite/rust/compile/issue-3898.rs
new file mode 100644 (file)
index 0000000..114370c
--- /dev/null
@@ -0,0 +1,112 @@
+// { dg-additional-options "-frust-compile-until=lowering" }
+
+#[lang = "sized"]
+trait Sized {}
+
+enum Result<T, E> {
+    Ok(T),
+    Err(E),
+}
+
+use Result::{Err, Ok};
+
+struct Utf8Error;
+
+const CONT_MASK: u8 = 15;
+const TAG_CONT_U8: u8 = 15;
+
+#[inline(always)]
+pub fn run_utf8_validation(v: &[u8]) -> Result<(), Utf8Error> {
+    let mut index = 0;
+    let len = 64;
+
+    let usize_bytes = 8;
+    let ascii_block_size = 2 * usize_bytes;
+    let blocks_end = if len >= ascii_block_size {
+        len - ascii_block_size + 1
+    } else {
+        0
+    };
+
+    while index < len {
+        let old_offset = index;
+        macro_rules! err {
+            ($error_len: expr) => {
+                return Err(Utf8Error)
+            };
+        }
+
+        macro_rules! next {
+            () => {{
+                index += 1;
+                // we needed data, but there was none: error!
+                if index >= len {
+                    err!(None)
+                }
+                v[index]
+            }};
+        }
+
+        let first = v[index];
+        if first >= 128 {
+            let w = 15;
+            // 2-byte encoding is for codepoints  \u{0080} to  \u{07ff}
+            //        first  C2 80        last DF BF
+            // 3-byte encoding is for codepoints  \u{0800} to  \u{ffff}
+            //        first  E0 A0 80     last EF BF BF
+            //   excluding surrogates codepoints  \u{d800} to  \u{dfff}
+            //               ED A0 80 to       ED BF BF
+            // 4-byte encoding is for codepoints \u{1000}0 to \u{10ff}ff
+            //        first  F0 90 80 80  last F4 8F BF BF
+            //
+            // Use the UTF-8 syntax from the RFC
+            //
+            // https://tools.ietf.org/html/rfc3629
+            // UTF8-1      = %x00-7F
+            // UTF8-2      = %xC2-DF UTF8-tail
+            // UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
+            //               %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
+            // UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
+            //               %xF4 %x80-8F 2( UTF8-tail )
+            match w {
+                2 => {
+                    if next!() & !CONT_MASK != TAG_CONT_U8 {
+                        err!(Some(1))
+                    }
+                }
+                3 => {
+                    match (first, next!()) {
+                        (0xE0, 0xA0..=0xBF)
+                        | (0xE1..=0xEC, 0x80..=0xBF)
+                        | (0xED, 0x80..=0x9F)
+                        | (0xEE..=0xEF, 0x80..=0xBF) => {}
+                        _ => err!(Some(1)),
+                    }
+                    if next!() & !CONT_MASK != TAG_CONT_U8 {
+                        err!(Some(2))
+                    }
+                }
+                4 => {
+                    match (first, next!()) {
+                        (0xF0, 0x90..=0xBF) | (0xF1..=0xF3, 0x80..=0xBF) | (0xF4, 0x80..=0x8F) => {}
+                        _ => err!(Some(1)),
+                    }
+                    if next!() & !CONT_MASK != TAG_CONT_U8 {
+                        err!(Some(2))
+                    }
+                    if next!() & !CONT_MASK != TAG_CONT_U8 {
+                        err!(Some(3))
+                    }
+                }
+                _ => err!(Some(1)),
+            }
+            index += 1;
+        } else {
+            index += 1;
+        }
+    }
+
+    Ok(())
+}
+
+fn main() {}