PY-21244 Properly enumerate nested fields without explicit name or index

Previously they were enumerated in the opposite order, i.e.
descendant fields had lower indexes than their parents. Existing tests
didn't caught that problem because I misinterpreted the method
UsefulTestCase#assertSameElements(), should have used
UsefulTestCase#assertOrderedEquals() instead.
This commit is contained in:
Mikhail Golubev
2016-11-08 18:20:22 +03:00
parent 9ffbd3bd87
commit df4bbd4a66
5 changed files with 58 additions and 49 deletions
@@ -117,6 +117,8 @@ public class PyNewStyleStringFormatParser {
private Field parseField(int startOffset, int recursionDepth) {
assert myNodeText.charAt(startOffset) == '{';
int autoFieldNumber = myImplicitlyNumberedFieldsCounter;
// in the order of appearance inside a field
final TIntArrayList attrAndLookupBounds = new TIntArrayList();
int conversionStart = -1;
@@ -155,6 +157,11 @@ public class PyNewStyleStringFormatParser {
if (!recovering) {
// avoid duplicate offsets in sequences like "]." or "]["
addIfNotLastItem(attrAndLookupBounds, offset);
// no name in the field, increment implicitly named fields counter
if (attrAndLookupBounds.size() == 1 && attrAndLookupBounds.get(0) == startOffset + 1) {
myImplicitlyNumberedFieldsCounter++;
}
}
if (c == ':') {
@@ -194,23 +201,18 @@ public class PyNewStyleStringFormatParser {
addIfNotLastItem(attrAndLookupBounds, contentEnd);
}
final Field field = new Field(myNodeText,
startOffset,
attrAndLookupBounds.toNativeArray(),
conversionStart,
formatSpecStart,
nestedFields,
rightBraceOffset,
rightBraceOffset == -1 ? contentEnd : rightBraceOffset + 1,
myImplicitlyNumberedFieldsCounter,
recursionDepth);
assert !attrAndLookupBounds.isEmpty();
if (attrAndLookupBounds.get(0) == startOffset + 1) {
myImplicitlyNumberedFieldsCounter++;
}
return field;
return new Field(myNodeText,
startOffset,
attrAndLookupBounds.toNativeArray(),
conversionStart,
formatSpecStart,
nestedFields,
rightBraceOffset,
rightBraceOffset == -1 ? contentEnd : rightBraceOffset + 1,
autoFieldNumber,
recursionDepth);
}
private static void addIfNotLastItem(TIntArrayList attrAndLookupBounds, int offset) {
@@ -0,0 +1 @@
'{:.{}}'.format(3.1415926, 3)
@@ -178,8 +178,8 @@ public class PyStringFormatParserTest extends UsefulTestCase {
final List<Field> topLevelFields = result.getFields();
assertSize(2, topLevelFields);
assertSize(5, result.getAllFields());
assertSameElements(result.getAllFields().stream().map(f -> f.getDepth()).toArray(), 1, 2, 3, 4, 1);
assertSameElements(result.getAllFields().stream().map(f -> f.getAutoPosition()).toArray(), 0, 1, 2, 3, 4);
assertOrderedEquals(result.getAllFields().stream().map(f -> f.getDepth()).toArray(), 1, 2, 3, 4, 1);
assertOrderedEquals(result.getAllFields().stream().map(f -> f.getAutoPosition()).toArray(), 0, 1, 2, 3, 4);
}
public void testNewStyleAttrAndLookups() {
@@ -195,40 +195,40 @@ public class PyStringFormatParserTest extends UsefulTestCase {
field = doParseAndGetFirstField("u'{foo.bar.baz}'");
assertEquals("foo", field.getFirstName());
assertSameElements(field.getAttributesAndLookups(), ".bar", ".baz");
assertOrderedEquals(field.getAttributesAndLookups(), ".bar", ".baz");
field = doParseAndGetFirstField("u'{foo[bar][baz]}'");
assertEquals("foo", field.getFirstName());
assertSameElements(field.getAttributesAndLookups(), "[bar]", "[baz]");
assertOrderedEquals(field.getAttributesAndLookups(), "[bar]", "[baz]");
field = doParseAndGetFirstField("u'{foo.bar[baz]}'");
assertEquals("foo", field.getFirstName());
assertSameElements(field.getAttributesAndLookups(), ".bar", "[baz]");
assertOrderedEquals(field.getAttributesAndLookups(), ".bar", "[baz]");
field = doParseAndGetFirstField("u'{foo.bar[baz}'");
assertEquals("foo", field.getFirstName());
assertSameElements(field.getAttributesAndLookups(), ".bar");
assertOrderedEquals(field.getAttributesAndLookups(), ".bar");
field = doParseAndGetFirstField("u'{foo[{bar[baz]}'");
assertEquals("foo", field.getFirstName());
assertSameElements(field.getAttributesAndLookups(), "[{bar[baz]");
assertOrderedEquals(field.getAttributesAndLookups(), "[{bar[baz]");
field = doParseAndGetFirstField("u'{foo[{} {0} {bar.baz}]'");
assertEquals("foo", field.getFirstName());
assertSameElements(field.getAttributesAndLookups(), "[{} {0} {bar.baz}]");
assertOrderedEquals(field.getAttributesAndLookups(), "[{} {0} {bar.baz}]");
field = doParseAndGetFirstField("u'{foo[bar]baz'");
assertEquals("foo", field.getFirstName());
assertSameElements(field.getAttributesAndLookups(), "[bar]");
assertOrderedEquals(field.getAttributesAndLookups(), "[bar]");
field = doParseAndGetFirstField("'{0[foo][.!:][}]}'");
assertEquals("0", field.getFirstName());
assertSameElements(field.getAttributesAndLookups(), "[foo]", "[.!:]", "[}]");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", "[.!:]", "[}]");
field = doParseAndGetFirstField("'{.foo.bar}'");
assertEmpty(field.getFirstName());
assertEquals(TextRange.create(2, 2), field.getFirstNameRange());
assertSameElements(field.getAttributesAndLookups(), ".foo", ".bar");
assertOrderedEquals(field.getAttributesAndLookups(), ".foo", ".bar");
field = doParseAndGetFirstField("'{}'");
assertEmpty(field.getFirstName());
@@ -261,68 +261,68 @@ public class PyStringFormatParserTest extends UsefulTestCase {
assertEmpty(field.getAttributesAndLookups());
field = doParseAndGetFirstField("'{0[foo].bar[baz]}'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
field = doParseAndGetFirstField("'{0[foo].bar[baz]'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
field = doParseAndGetFirstField("'{0[foo].bar[baz]");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
// do not recover unfinished lookups
field = doParseAndGetFirstField("'{0[foo].bar[ba}'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar");
field = doParseAndGetFirstField("'{0[foo].bar[ba!}'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar");
field = doParseAndGetFirstField("'{0[foo].bar[ba:}'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar");
field = doParseAndGetFirstField("'{0[foo].bar[ba'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar");
field = doParseAndGetFirstField("'{0[foo].bar[ba");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar");
// do not recover illegal attributes
field = doParseAndGetFirstField("'{0[foo].bar[baz]quux}'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
field = doParseAndGetFirstField("'{0[foo].bar[baz]quux!}'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
field = doParseAndGetFirstField("'{0[foo].bar[baz]quux:}'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
field = doParseAndGetFirstField("'{0[foo].bar[baz]quux'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
field = doParseAndGetFirstField("'{0[foo].bar[baz]quux");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar", "[baz]");
field = doParseAndGetFirstField("'{0..}'");
assertEquals("0", field.getFirstName());
assertSameElements(field.getAttributesAndLookups(), ".", ".");
assertOrderedEquals(field.getAttributesAndLookups(), ".", ".");
// recover attributes
field = doParseAndGetFirstField("'{0[foo].}'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".");
field = doParseAndGetFirstField("'{0[foo].'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".");
field = doParseAndGetFirstField("'{0[foo].");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".");
field = doParseAndGetFirstField("'{0[foo].bar}'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar");
field = doParseAndGetFirstField("'{0[foo].bar'");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar");
field = doParseAndGetFirstField("'{0[foo].bar");
assertSameElements(field.getAttributesAndLookups(), "[foo]", ".bar");
assertOrderedEquals(field.getAttributesAndLookups(), "[foo]", ".bar");
}
public void testAutoPosition() {
@@ -361,12 +361,12 @@ public class PyStringFormatParserTest extends UsefulTestCase {
public void testNewStyleNamedUnicodeEscapeInLookup() {
final Field field = doParseAndGetFirstField("'{foo[\\N{ESCAPE WITH ]}]}'");
assertSameElements(field.getAttributesAndLookups(), "[\\N{ESCAPE WITH ]}]");
assertOrderedEquals(field.getAttributesAndLookups(), "[\\N{ESCAPE WITH ]}]");
}
public void testNewStyleNamedUnicodeEscapeInAttribute() {
final Field field = doParseAndGetFirstField("'{foo.b\\N{ESCAPE WITH [}.b\\N{ESCAPE WITH .}}'");
assertSameElements(field.getAttributesAndLookups(), ".b\\N{ESCAPE WITH [}", ".b\\N{ESCAPE WITH .}");
assertOrderedEquals(field.getAttributesAndLookups(), ".b\\N{ESCAPE WITH [}", ".b\\N{ESCAPE WITH .}");
}
public void testNewStyleUnclosedLookupEndsWithRightBrace() {
@@ -158,4 +158,9 @@ public class PyConvertToFStringIntentionTest extends PyIntentionTestCase {
public void testFormatMethodNestedFields2() {
doTest();
}
// PY-21244
public void testFormatMethodNestedFields3() {
doTest();
}
}