RegExp: improve parser error recovery (IDEA-199135)

This commit is contained in:
Bas Leijdekkers
2018-09-19 16:54:38 +02:00
parent c54a4aba18
commit 5d35b47d3d
20 changed files with 490 additions and 585 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -38,11 +38,12 @@ public class RegExpParser implements PsiParser, LightPsiParser {
public void parseLight(IElementType root, PsiBuilder builder) {
final PsiBuilder.Marker rootMarker = builder.mark();
parsePattern(builder);
while (!builder.eof()) {
while (true) {
parsePattern(builder);
if (builder.eof()) break;
patternExpected(builder);
builder.advanceLexer();
if (builder.eof()) break;
}
rootMarker.done(root);
@@ -59,49 +60,40 @@ public class RegExpParser implements PsiParser, LightPsiParser {
/**
* PATTERN ::= BRANCH "|" PATTERN | BRANCH
*/
private boolean parsePattern(PsiBuilder builder) {
private void parsePattern(PsiBuilder builder) {
final PsiBuilder.Marker marker = builder.mark();
if (!parseBranch(builder)) {
marker.drop();
return false;
}
parseBranch(builder);
while (builder.getTokenType() == RegExpTT.UNION) {
builder.advanceLexer();
if (!parseBranch(builder)) {
patternExpected(builder);
break;
}
parseBranch(builder);
}
marker.done(RegExpElementTypes.PATTERN);
return true;
}
/**
* BRANCH ::= ATOM BRANCH | ""
*/
private boolean parseBranch(PsiBuilder builder) {
private void parseBranch(PsiBuilder builder) {
final PsiBuilder.Marker marker = builder.mark();
if (!parseAtom(builder)) {
while (!parseAtom(builder)) {
final IElementType token = builder.getTokenType();
if (token == RegExpTT.GROUP_END || token == RegExpTT.UNION || token == null) {
// empty branches are allowed
marker.done(RegExpElementTypes.BRANCH);
return true;
return;
}
marker.drop();
return false;
patternExpected(builder);
builder.advanceLexer();
}
//noinspection StatementWithEmptyBody
while (parseAtom(builder)) {}
marker.done(RegExpElementTypes.BRANCH);
return true;
}
/**
@@ -424,18 +416,12 @@ public class RegExpParser implements PsiParser, LightPsiParser {
builder.error("Group name or number expected");
}
checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group reference");
if (!parseBranch(builder)) {
patternExpected(builder);
}
else {
if (builder.getTokenType() == RegExpTT.UNION) {
builder.advanceLexer();
if (!parseBranch(builder)) {
patternExpected(builder);
}
}
checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group");
parseBranch(builder);
if (builder.getTokenType() == RegExpTT.UNION) {
builder.advanceLexer();
parseBranch(builder);
}
checkMatches(builder, RegExpTT.GROUP_END, "Unclosed conditional");
marker.done(RegExpElementTypes.PY_COND_REF);
}
else if (type == RegExpTT.PROPERTY) {
@@ -458,12 +444,8 @@ public class RegExpParser implements PsiParser, LightPsiParser {
}
private void parseGroupEnd(PsiBuilder builder) {
if (!parsePattern(builder)) {
patternExpected(builder);
}
else {
checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group");
}
parsePattern(builder);
checkMatches(builder, RegExpTT.GROUP_END, "Unclosed group");
}
private static void parseNamedGroupRef(PsiBuilder builder, PsiBuilder.Marker marker, IElementType type) {
@@ -474,6 +456,7 @@ public class RegExpParser implements PsiParser, LightPsiParser {
}
private static boolean isLetter(CharSequence text) {
if (text == null) return false;
assert text.length() == 1;
final char c = text.charAt(0);
return AsciiUtil.isLetter(c);

View File

@@ -420,32 +420,32 @@ HEX_CHAR=[0-9a-fA-F]
":" { yybegin(YYINITIAL); return RegExpTT.COLON; }
")" { yybegin(YYINITIAL); return RegExpTT.GROUP_END; }
{ANY} { yybegin(YYINITIAL); return RegExpTT.BAD_CHARACTER; }
{ANY} { yybegin(YYINITIAL); yypushback(1); }
}
<NAMED_GROUP> {
{GROUP_NAME} { return RegExpTT.NAME; }
">" { yybegin(YYINITIAL); return RegExpTT.GT; }
{ANY} { yybegin(YYINITIAL); return RegExpTT.BAD_CHARACTER; }
{ANY} { yybegin(YYINITIAL); yypushback(1); }
}
<QUOTED_NAMED_GROUP> {
{GROUP_NAME} { return RegExpTT.NAME; }
"'" { yybegin(YYINITIAL); return RegExpTT.QUOTE; }
{ANY} { yybegin(YYINITIAL); return RegExpTT.BAD_CHARACTER; }
{ANY} { yybegin(YYINITIAL); yypushback(1); }
}
<PY_NAMED_GROUP_REF> {
{GROUP_NAME} { return RegExpTT.NAME; }
")" { yybegin(YYINITIAL); return RegExpTT.GROUP_END; }
{ANY} { yybegin(YYINITIAL); return RegExpTT.BAD_CHARACTER; }
{ANY} { yybegin(YYINITIAL); yypushback(1); }
}
<PY_COND_REF> {
{GROUP_NAME} { return RegExpTT.NAME; }
[:digit:]+ { return RegExpTT.NUMBER; }
")" { yybegin(YYINITIAL); return RegExpTT.GROUP_END; }
{ANY} { yybegin(YYINITIAL); return RegExpTT.BAD_CHARACTER; }
{ANY} { yybegin(YYINITIAL); yypushback(1); }
}
"^" { return RegExpTT.CARET; }

View File

@@ -71,6 +71,7 @@ public class RegExpParsingTest extends ParsingTestCase {
public void testSimple20() throws IOException { doCodeTest("a{1,2}"); }
public void testSimple21() throws IOException { doCodeTest("a{1,foo}"); }
public void testSimple22() throws IOException { doCodeTest("\\;"); }
public void testSimple23() throws IOException { doCodeTest(""); }
public void testQuantifiers1() throws IOException { doCodeTest("a?"); }
public void testQuantifiers2() throws IOException { doCodeTest("a+"); }
@@ -308,6 +309,7 @@ public class RegExpParsingTest extends ParsingTestCase {
public void testOptions1() throws IOException { doCodeTest("(?iZm)abc"); }
public void testOptions2() throws IOException { doCodeTest("(?idmsuxU)nice"); }
public void testOptions3() throws IOException { doCodeTest("(?idm-suxU)one(?suxU-idm)two"); }
public void testOptions4() throws IOException { doCodeTest("(?i|abc"); }
public void testTests1() throws IOException { doCodeTest("abc)"); }
public void testTests2() throws IOException { doCodeTest("(abc"); }

View File

@@ -1,4 +1,6 @@
JS_UNICODE_REGEXP_FILE
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(LBRACE)('{')
RegExpPatternImpl: <{>
RegExpBranchImpl: <{>
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(LBRACE)('{')

View File

@@ -1,4 +1,6 @@
JS_UNICODE_REGEXP_FILE
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(RBRACE)('}')
RegExpPatternImpl: <}>
RegExpBranchImpl: <}>
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(RBRACE)('}')

View File

@@ -1,4 +1,6 @@
JS_UNICODE_REGEXP_FILE
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(CLASS_END)(']')
RegExpPatternImpl: <]>
RegExpBranchImpl: <]>
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(CLASS_END)(']')

View File

@@ -1,4 +1,6 @@
REGEXP_FILE
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(LBRACE)('{')
RegExpPatternImpl: <{>
RegExpBranchImpl: <{>
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(LBRACE)('{')

View File

@@ -5,6 +5,12 @@ REGEXP_FILE
PsiErrorElement:Unmatched closing ')'
<empty list>
PsiElement(GROUP_END)(')')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(GROUP_BEGIN)('(')
RegExpPatternImpl: <(>
RegExpBranchImpl: <(>
RegExpGroupImpl: <(>
PsiElement(GROUP_BEGIN)('(')
RegExpPatternImpl: <>
RegExpBranchImpl: <>
<empty list>
PsiErrorElement:Unclosed group
<empty list>

View File

@@ -1,16 +1,14 @@
REGEXP_FILE
RegExpPatternImpl: <(?i:*>
RegExpBranchImpl: <(?i:*>
RegExpClosureImpl: <(?i:*>
RegExpGroupImpl: <(?i:>
PsiElement(SET_OPTIONS)('(?')
RegExpOptionsImpl: <i>
PsiElement(OPTIONS_ON)('i')
PsiElement(COLON)(':')
PsiErrorElement:Dangling metacharacter
<empty list>
RegExpQuantifierImpl: <*>
PsiElement(STAR)('*')
PsiErrorElement:Unmatched closing ')'
<empty list>
PsiElement(GROUP_END)(')')
RegExpPatternImpl: <(?i:*)>
RegExpBranchImpl: <(?i:*)>
RegExpGroupImpl: <(?i:*)>
PsiElement(SET_OPTIONS)('(?')
RegExpOptionsImpl: <i>
PsiElement(OPTIONS_ON)('i')
PsiElement(COLON)(':')
RegExpPatternImpl: <*>
RegExpBranchImpl: <*>
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(STAR)('*')
PsiElement(GROUP_END)(')')

View File

@@ -1,14 +1,14 @@
REGEXP_FILE
RegExpPatternImpl: <(?P<name>{>
RegExpBranchImpl: <(?P<name>{>
RegExpClosureImpl: <(?P<name>{>
RegExpGroupImpl: <(?P<name>>
PsiElement(PYTHON_NAMED_GROUP)('(?P<')
PsiElement(NAME)('name')
PsiElement(GT)('>')
PsiErrorElement:Dangling metacharacter
<empty list>
RegExpQuantifierImpl: <{>
PsiElement(LBRACE)('{')
PsiErrorElement:Number expected
<empty list>
RegExpGroupImpl: <(?P<name>{>
PsiElement(PYTHON_NAMED_GROUP)('(?P<')
PsiElement(NAME)('name')
PsiElement(GT)('>')
RegExpPatternImpl: <{>
RegExpBranchImpl: <{>
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(LBRACE)('{')
PsiErrorElement:Unclosed group
<empty list>

View File

@@ -1,38 +1,37 @@
REGEXP_FILE
RegExpPatternImpl: <(?(name)yes-pattern|{>
RegExpBranchImpl: <(?(name)yes-pattern|{>
RegExpClosureImpl: <(?(name)yes-pattern|{>
RegExpPyCondRefImpl: <(?(name)yes-pattern|>
PsiElement(PYTHON_COND_REF)('(?(')
PsiElement(NAME)('name')
PsiElement(GROUP_END)(')')
RegExpBranchImpl: <yes-pattern>
RegExpCharImpl: <y>
PsiElement(CHARACTER)('y')
RegExpCharImpl: <e>
PsiElement(CHARACTER)('e')
RegExpCharImpl: <s>
PsiElement(CHARACTER)('s')
RegExpCharImpl: <->
PsiElement(CHARACTER)('-')
RegExpCharImpl: <p>
PsiElement(CHARACTER)('p')
RegExpCharImpl: <a>
PsiElement(CHARACTER)('a')
RegExpCharImpl: <t>
PsiElement(CHARACTER)('t')
RegExpCharImpl: <t>
PsiElement(CHARACTER)('t')
RegExpCharImpl: <e>
PsiElement(CHARACTER)('e')
RegExpCharImpl: <r>
PsiElement(CHARACTER)('r')
RegExpCharImpl: <n>
PsiElement(CHARACTER)('n')
PsiElement(UNION)('|')
RegExpPyCondRefImpl: <(?(name)yes-pattern|{>
PsiElement(PYTHON_COND_REF)('(?(')
PsiElement(NAME)('name')
PsiElement(GROUP_END)(')')
RegExpBranchImpl: <yes-pattern>
RegExpCharImpl: <y>
PsiElement(CHARACTER)('y')
RegExpCharImpl: <e>
PsiElement(CHARACTER)('e')
RegExpCharImpl: <s>
PsiElement(CHARACTER)('s')
RegExpCharImpl: <->
PsiElement(CHARACTER)('-')
RegExpCharImpl: <p>
PsiElement(CHARACTER)('p')
RegExpCharImpl: <a>
PsiElement(CHARACTER)('a')
RegExpCharImpl: <t>
PsiElement(CHARACTER)('t')
RegExpCharImpl: <t>
PsiElement(CHARACTER)('t')
RegExpCharImpl: <e>
PsiElement(CHARACTER)('e')
RegExpCharImpl: <r>
PsiElement(CHARACTER)('r')
RegExpCharImpl: <n>
PsiElement(CHARACTER)('n')
PsiElement(UNION)('|')
RegExpBranchImpl: <{>
PsiErrorElement:Dangling metacharacter
<empty list>
RegExpQuantifierImpl: <{>
PsiElement(LBRACE)('{')
PsiErrorElement:Number expected
<empty list>
PsiErrorElement:Unclosed conditional
<empty list>

View File

@@ -50,7 +50,7 @@ REGEXP_FILE
PsiElement(CHARACTER)('r')
RegExpCharImpl: <n>
PsiElement(CHARACTER)('n')
PsiErrorElement:Unclosed group
PsiErrorElement:Unclosed conditional
<empty list>
PsiElement(UNION)('|')
RegExpBranchImpl: <maybe-pattern>

View File

@@ -1,16 +1,13 @@
REGEXP_FILE
RegExpPatternImpl: <(*>
RegExpBranchImpl: <(*>
RegExpClosureImpl: <(*>
RegExpGroupImpl: <(>
PsiElement(GROUP_BEGIN)('(')
PsiErrorElement:Dangling metacharacter
<empty list>
RegExpQuantifierImpl: <*>
PsiElement(STAR)('*')
PsiErrorElement:Unmatched closing ')'
<empty list>
PsiElement(GROUP_END)(')')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(CHARACTER)('b')
RegExpPatternImpl: <(*)b>
RegExpBranchImpl: <(*)b>
RegExpGroupImpl: <(*)>
PsiElement(GROUP_BEGIN)('(')
RegExpPatternImpl: <*>
RegExpBranchImpl: <*>
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(STAR)('*')
PsiElement(GROUP_END)(')')
RegExpCharImpl: <b>
PsiElement(CHARACTER)('b')

View File

@@ -1,9 +1,10 @@
REGEXP_FILE
RegExpPatternImpl: <a|>
RegExpPatternImpl: <a|*>
RegExpBranchImpl: <a>
RegExpCharImpl: <a>
PsiElement(CHARACTER)('a')
PsiElement(UNION)('|')
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(STAR)('*')
RegExpBranchImpl: <*>
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(STAR)('*')

View File

@@ -0,0 +1,17 @@
REGEXP_FILE
RegExpPatternImpl: <(?i|abc>
RegExpBranchImpl: <(?i>
RegExpSetOptionsImpl: <(?i>
PsiElement(SET_OPTIONS)('(?')
RegExpOptionsImpl: <i>
PsiElement(OPTIONS_ON)('i')
PsiErrorElement:Unclosed options group
<empty list>
PsiElement(UNION)('|')
RegExpBranchImpl: <abc>
RegExpCharImpl: <a>
PsiElement(CHARACTER)('a')
RegExpCharImpl: <b>
PsiElement(CHARACTER)('b')
RegExpCharImpl: <c>
PsiElement(CHARACTER)('c')

View File

@@ -6,6 +6,7 @@ REGEXP_FILE
PsiErrorElement:Unmatched closing ')'
<empty list>
PsiElement(GROUP_END)(')')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(CHARACTER)('b')
RegExpPatternImpl: <b>
RegExpBranchImpl: <b>
RegExpCharImpl: <b>
PsiElement(CHARACTER)('b')

View File

@@ -1,6 +1,6 @@
REGEXP_FILE
RegExpPatternImpl: <\s*@return(?:s)?\s*(?:(?:\{|:)?\s*(?(>
RegExpBranchImpl: <\s*@return(?:s)?\s*(?:(?:\{|:)?\s*(?(>
RegExpPatternImpl: <\s*@return(?:s)?\s*(?:(?:\{|:)?\s*(?([^\s\}]+)\s*\}?\s*)?(.*)>
RegExpBranchImpl: <\s*@return(?:s)?\s*(?:(?:\{|:)?\s*(?([^\s\}]+)\s*\}?\s*)?(.*)>
RegExpClosureImpl: <\s*>
RegExpSimpleClassImpl: <\s>
PsiElement(CHAR_CLASS)('\s')
@@ -35,85 +35,72 @@ REGEXP_FILE
PsiElement(CHAR_CLASS)('\s')
RegExpQuantifierImpl: <*>
PsiElement(STAR)('*')
RegExpGroupImpl: <(?:(?:\{|:)?\s*(?(>
PsiElement(NON_CAPT_GROUP)('(?:')
RegExpPatternImpl: <(?:\{|:)?\s*(?(>
RegExpBranchImpl: <(?:\{|:)?\s*(?(>
RegExpClosureImpl: <(?:\{|:)?>
RegExpGroupImpl: <(?:\{|:)>
PsiElement(NON_CAPT_GROUP)('(?:')
RegExpPatternImpl: <\{|:>
RegExpBranchImpl: <\{>
RegExpCharImpl: <\{>
PsiElement(ESC_CHARACTER)('\{')
PsiElement(UNION)('|')
RegExpBranchImpl: <:>
RegExpCharImpl: <:>
PsiElement(CHARACTER)(':')
RegExpClosureImpl: <(?:(?:\{|:)?\s*(?([^\s\}]+)\s*\}?\s*)?>
RegExpGroupImpl: <(?:(?:\{|:)?\s*(?([^\s\}]+)\s*\}?\s*)>
PsiElement(NON_CAPT_GROUP)('(?:')
RegExpPatternImpl: <(?:\{|:)?\s*(?([^\s\}]+)\s*\}?\s*>
RegExpBranchImpl: <(?:\{|:)?\s*(?([^\s\}]+)\s*\}?\s*>
RegExpClosureImpl: <(?:\{|:)?>
RegExpGroupImpl: <(?:\{|:)>
PsiElement(NON_CAPT_GROUP)('(?:')
RegExpPatternImpl: <\{|:>
RegExpBranchImpl: <\{>
RegExpCharImpl: <\{>
PsiElement(ESC_CHARACTER)('\{')
PsiElement(UNION)('|')
RegExpBranchImpl: <:>
RegExpCharImpl: <:>
PsiElement(CHARACTER)(':')
PsiElement(GROUP_END)(')')
RegExpQuantifierImpl: <?>
PsiElement(QUEST)('?')
RegExpClosureImpl: <\s*>
RegExpSimpleClassImpl: <\s>
PsiElement(CHAR_CLASS)('\s')
RegExpQuantifierImpl: <*>
PsiElement(STAR)('*')
RegExpPyCondRefImpl: <(?([^\s\}]+)>
PsiElement(PYTHON_COND_REF)('(?(')
PsiErrorElement:Group name or number expected
<empty list>
RegExpBranchImpl: <[^\s\}]+>
RegExpClosureImpl: <[^\s\}]+>
RegExpClassImpl: <[^\s\}]>
PsiElement(CLASS_BEGIN)('[')
PsiElement(CARET)('^')
RegExpSimpleClassImpl: <\s>
PsiElement(CHAR_CLASS)('\s')
RegExpCharImpl: <\}>
PsiElement(REDUNDANT_ESCAPE)('\}')
PsiElement(CLASS_END)(']')
RegExpQuantifierImpl: <+>
PsiElement(PLUS)('+')
PsiElement(GROUP_END)(')')
RegExpQuantifierImpl: <?>
PsiElement(QUEST)('?')
RegExpClosureImpl: <\s*>
RegExpSimpleClassImpl: <\s>
PsiElement(CHAR_CLASS)('\s')
RegExpClosureImpl: <\s*>
RegExpSimpleClassImpl: <\s>
PsiElement(CHAR_CLASS)('\s')
RegExpQuantifierImpl: <*>
PsiElement(STAR)('*')
RegExpClosureImpl: <\}?>
RegExpCharImpl: <\}>
PsiElement(REDUNDANT_ESCAPE)('\}')
RegExpQuantifierImpl: <?>
PsiElement(QUEST)('?')
RegExpClosureImpl: <\s*>
RegExpSimpleClassImpl: <\s>
PsiElement(CHAR_CLASS)('\s')
RegExpQuantifierImpl: <*>
PsiElement(STAR)('*')
PsiElement(GROUP_END)(')')
RegExpQuantifierImpl: <?>
PsiElement(QUEST)('?')
RegExpGroupImpl: <(.*)>
PsiElement(GROUP_BEGIN)('(')
RegExpPatternImpl: <.*>
RegExpBranchImpl: <.*>
RegExpClosureImpl: <.*>
RegExpSimpleClassImpl: <.>
PsiElement(DOT)('.')
RegExpQuantifierImpl: <*>
PsiElement(STAR)('*')
RegExpPyCondRefImpl: <(?(>
PsiElement(PYTHON_COND_REF)('(?(')
PsiErrorElement:Group name or number expected
<empty list>
PsiElement(BAD_CHARACTER)('[')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(CARET)('^')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(CHAR_CLASS)('\s')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(REDUNDANT_ESCAPE)('\}')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(CHARACTER)(']')
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(PLUS)('+')
PsiErrorElement:Unmatched closing ')'
<empty list>
PsiElement(GROUP_END)(')')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(CHAR_CLASS)('\s')
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(STAR)('*')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(REDUNDANT_ESCAPE)('\}')
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(QUEST)('?')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(CHAR_CLASS)('\s')
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(STAR)('*')
PsiErrorElement:Unmatched closing ')'
<empty list>
PsiElement(GROUP_END)(')')
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(QUEST)('?')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(GROUP_BEGIN)('(')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(DOT)('.')
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(STAR)('*')
PsiErrorElement:Unmatched closing ')'
<empty list>
PsiElement(GROUP_END)(')')
PsiElement(GROUP_END)(')')

View File

@@ -1,7 +1,8 @@
REGEXP_FILE
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(STAR)('*')
PsiErrorElement:Pattern expected
<empty list>
PsiElement(CHARACTER)('a')
RegExpPatternImpl: <*a>
RegExpBranchImpl: <*a>
PsiErrorElement:Dangling metacharacter
<empty list>
PsiElement(STAR)('*')
RegExpCharImpl: <a>
PsiElement(CHARACTER)('a')

View File

@@ -0,0 +1,4 @@
REGEXP_FILE
RegExpPatternImpl: <>
RegExpBranchImpl: <>
<empty list>