1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-30 12:25:35 +02:00

Bug 543038 - Implement brace elision for aggregate initialization

Change-Id: I72e92c8c196bf201c8edfde64598a7318d2fdfeb
Signed-off-by: Hannes Vogt <hannes@havogt.de>
This commit is contained in:
Hannes Vogt 2018-12-05 20:17:28 +01:00 committed by Nathan Ridge
parent d8c2330efe
commit 0757b45da5
5 changed files with 347 additions and 114 deletions

View file

@ -12843,11 +12843,150 @@ public class AST2CPPTests extends AST2CPPTestBase {
//
// int main() {
// type(1, 2);
// type{1, 2};
// type(other_type());
// }
public void testCtorWithWrongArguments_543913() throws Exception {
BindingAssertionHelper bh = getAssertionHelper();
bh.assertImplicitName("type(1, 2)", 4, IProblemBinding.class);
bh.assertImplicitName("type{1, 2}", 4, IProblemBinding.class);
bh.assertImplicitName("type(other_type())", 4, IProblemBinding.class);
}
// struct array{
// int data[1];
// };
//
// void foo(array) {}
//
// int main() {
// array{{1}};
// array{1};
// array a = {1};
// foo({1});
// foo({{1}});
// }
public void testBraceElisionForAggregateInit0_SimpleValid_543038() throws Exception {
parseAndCheckBindings();
}
// struct array{
// int data[1];
// };
//
// void foo(array) {}
//
// int main() {
// array{{1,2}};
// array{1,2};
// array a0 = {1,2};
// foo({1,2});
// foo({{1,2}});
// }
public void testBraceElisionForAggregateInit1_SimpleTooManyInitializers_543038() throws Exception {
BindingAssertionHelper bh = getAssertionHelper();
// bh.assertProblem("array{{1,2}}", 5); // TODO not implemented
// bh.assertProblem("array{1,2}", 5); // TODO not implemented
bh.assertImplicitName("a0", 2, IProblemBinding.class);
bh.assertProblem("foo({1,2})", 3);
bh.assertProblem("foo({{1,2}})", 3);
}
// struct level0{
// int a;
// int b;
// };
//
// struct level1{
// public:
// level1(level0 a): a(a){}
// private:
// level0 a;
// };
//
// struct level2{
// level1 data;
// };
//
// void foo(level2) {}
//
// int main() {
// level1{{1,2}}; // ok
// foo({level1{{1,2}}}); // ok
// level1{1,2}; // ERROR: calling level1 constructor, not aggregate init of level0
// foo({{{1,2,3}}}); // ERROR: not aggregate init
// }
public void testBraceElisionForAggregateInit2_WithNonAggregate_543038() throws Exception {
BindingAssertionHelper bh = getAssertionHelper();
bh.assertNonProblem("foo({level1{{1,2}}})", 3);
ICPPConstructor ctor = bh.assertNonProblem("level1(level0 a)", "level1");
ICPPASTSimpleTypeConstructorExpression typeConstructorExpr = bh.assertNode("level1{{1,2}};", "level1{{1,2}}");
IASTImplicitName[] implicitNames = ((IASTImplicitNameOwner) typeConstructorExpr).getImplicitNames();
assertEquals(ctor, implicitNames[0].resolveBinding());
bh.assertImplicitName("level1{1,2};", 6, IProblemBinding.class);
bh.assertProblem("foo({{{1,2,3}}}", 3);
}
// struct array2D{
// int data[2][3];
// };
//
// void foo(array2D) {}
//
// int main() {
// foo({{{1,2,3},{1,2,3}}}); // no elision
// foo({{1,2,3,1,2,3}}); // eliding one level
// foo({{1,2}}); // eliding one level, but with only 2 elements which seems to initialize the outer level
// foo({1,2,3,1,2,3}); // eliding all levels
// foo({{1,2,3},{1,2,3}}); // ERROR eliding outer-most is not allowed
// }
public void testBraceElisionForAggregateInit3_543038() throws Exception {
BindingAssertionHelper bh = getAssertionHelper();
bh.assertNonProblem("foo({{{1,2,3},{1,2,3}}});", 3);
bh.assertNonProblem("foo({{1,2,3,1,2,3}});", 3);
bh.assertNonProblem("foo({{1,2}});", 3);
bh.assertNonProblem("foo({1,2,3,1,2,3});", 3);
bh.assertProblem("foo({{1,2,3},{1,2,3}});", 3);
}
// struct type{
// type(int){};
// };
//
// struct array{
// type data[2];
// };
//
// void foo(array){}
//
// int main() {
// foo({type{1},type{2}});
// foo({type{1}}); // ERROR: type is not default constructible
// }
public void testBraceElisionForAggregateInit4_nonDefaultConstructible_543038() throws Exception {
BindingAssertionHelper bh = getAssertionHelper();
bh.assertNonProblem("foo({type{1},type{2}});", 3);
bh.assertProblem("foo({type{1}});", 3);
}
// struct array2D{
// int data[2][3];
// };
//
// void foo(array2D) {}
//
// int main() {
// foo({{{1,2,3},1,2,3}}); // ok: data[0] is initialized without elision, data[1] with elision
// foo({{1,2,3,{1,2,3}}}); // ok: data[1] is initialized without elision, data[0] with elision
// foo({{1,2,{1,2,3}}}); // ERROR: trying to initialize data[0][2] with {1,2,3}
// }
public void testBraceElisionForAggregateInit5_partlyEliding_543038() throws Exception {
BindingAssertionHelper bh = getAssertionHelper();
bh.assertNonProblem("foo({{{1,2,3},1,2,3}});", 3);
bh.assertNonProblem("foo({{1,2,3,{1,2,3}}});", 3);
bh.assertProblem("foo({{1,2,{1,2,3}}});", 3);
}
}

View file

@ -150,12 +150,18 @@ public class ProblemBinding extends PlatformObject implements IProblemBinding, I
@Override
public String getName() {
return node instanceof IASTName ? new String(((IASTName) node).getSimpleID()) : CPPSemantics.EMPTY_NAME;
if (node instanceof IASTName)
return new String(((IASTName) node).getSimpleID());
else
return arg != null ? new String(arg) : CPPSemantics.EMPTY_NAME;
}
@Override
public char[] getNameCharArray() {
return node instanceof IASTName ? ((IASTName) node).getSimpleID() : CharArrayUtils.EMPTY;
if (node instanceof IASTName)
return ((IASTName) node).getSimpleID();
else
return arg != null ? arg : CharArrayUtils.EMPTY;
}
@Override

View file

@ -0,0 +1,182 @@
/*******************************************************************************
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.cdt.internal.core.dom.parser.cpp.semantics;
import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IArrayType;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.IValue;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPField;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPReferenceType;
import org.eclipse.cdt.core.parser.util.ArrayUtil;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPEvaluation;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.Conversions.Context;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.Conversions.UDCMode;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.Cost.Rank;
class AggregateInitialization {
private ICPPEvaluation[] fInitializers;
private int fIndex = 0;
/**
* Checks whether 'target' can be initialized from 'list' according to the rules for
* aggregate initialization ([dcl.init.aggr]).
*/
public static Cost check(IType target, EvalInitList list) throws DOMException {
return new AggregateInitialization().checkImpl(target, list);
}
private AggregateInitialization() {
}
private Cost checkImpl(IType target, EvalInitList list) throws DOMException {
fInitializers = list.getClauses();
fIndex = 0;
Cost worstCost = new Cost(list.getType(), target, Rank.IDENTITY);
Cost cost = checkInitializationOfElements(target, worstCost);
if (!cost.converts())
return cost;
if (fIndex < fInitializers.length)
// p7: An initializer-list is ill-formed if the number of initializer-clauses exceeds
// the number of members to initialize.
return Cost.NO_CONVERSION;
else
return worstCost;
}
/**
* If no braces are elided, check initialization of element by taking the next clause from the EvalInitList,
* else recurses into the subaggregate.
*/
private Cost checkElement(IType type, IValue initialValue, Cost worstCost) throws DOMException {
if (fIndex >= fInitializers.length)
// TODO for arrays we could short-circuit default init instead of trying to init each element
return checkInitializationFromDefaultMemberInitializer(type, initialValue, worstCost);
worstCost = new Cost(fInitializers[fIndex].getType(), type, Rank.IDENTITY);
if (fInitializers[fIndex].isInitializerList() || !isAggregate(type)) { // no braces are elided
// p3: The elements of the initializer list are taken as initializers for the elements
// of the aggregate, in order.
ICPPEvaluation initializer = fInitializers[fIndex];
fIndex++;
Cost cost = Conversions.checkImplicitConversionSequence(type, initializer.getType(),
initializer.getValueCategory(), UDCMode.ALLOWED, Context.ORDINARY);
if (!cost.converts()) {
return cost;
}
// If the initializer-clause is an expression and a narrowing conversion is
// required to convert the expression, the program is ill-formed.
if (!(initializer instanceof EvalInitList) && cost.isNarrowingConversion()) {
return Cost.NO_CONVERSION;
}
if (cost.compareTo(worstCost) > 0) {
worstCost = cost;
}
} else { // braces are elided: need to check on subaggregates
Cost cost = checkInitializationOfElements(type, worstCost);
if (!cost.converts())
return cost;
if (cost.compareTo(worstCost) > 0) {
worstCost = cost;
}
}
return worstCost;
}
/**
* checkElement() for each element of an array or each field of a class aggregate.
*/
private Cost checkInitializationOfElements(IType type, Cost worstCost) throws DOMException {
if (type instanceof ICPPClassType && TypeTraits.isAggregateClass((ICPPClassType) type)) {
ICPPField[] fields = getFieldsForAggregateInitialization((ICPPClassType) type);
for (ICPPField field : fields) {
Cost cost = checkElement(field.getType(), field.getInitialValue(), worstCost);
if (!cost.converts())
return cost;
if (cost.compareTo(worstCost) > 0) {
worstCost = cost;
}
}
} else if (type instanceof IArrayType) {
IArrayType arrayType = (IArrayType) type;
Number arraySize = arrayType.getSize().numberValue();
if (arraySize != null)
for (long i = 0; i < arraySize.longValue(); i++) {
Cost cost = checkElement(arrayType.getType(), null, worstCost);
if (!cost.converts())
return cost;
if (cost.compareTo(worstCost) > 0) {
worstCost = cost;
}
}
}
return worstCost;
}
/**
* p8: If there are fewer initializer-clauses than there are elements in the
* aggregate, then each element not explicitly initialized shall be
* initialized from its default member initializer or, if there is no
* default member initializer, from an empty initializer list.
*/
private Cost checkInitializationFromDefaultMemberInitializer(IType type, IValue initialValue, Cost worstCost)
throws DOMException {
if (initialValue != null) {
return worstCost; // has a default member initializer
}
// p11: If an incomplete or empty initializer-list leaves a member of
// reference type uninitialized, the program is ill-formed.
IType fieldType = SemanticUtil.getNestedType(type, SemanticUtil.TDEF);
if (fieldType instanceof ICPPReferenceType) {
return Cost.NO_CONVERSION;
}
// Empty initializer list
EvalInitList emptyInit = new EvalInitList(ICPPEvaluation.EMPTY_ARRAY, CPPSemantics.getCurrentLookupPoint());
Cost cost = Conversions.listInitializationSequence(emptyInit, fieldType, UDCMode.ALLOWED, false);
return cost;
}
private static boolean isAggregate(IType type) {
return (type instanceof ICPPClassType && TypeTraits.isAggregateClass((ICPPClassType) type))
|| type instanceof IArrayType;
}
/**
* Get those fields of 'targetClass' which participate in aggregate initialization.
* These are the declared fields, excluding static fields and anonymous bit-fields
* ([decl.init.aggr] p6).
*/
private static ICPPField[] getFieldsForAggregateInitialization(ICPPClassType targetClass) {
ICPPField[] fields = targetClass.getDeclaredFields();
ICPPField[] result = fields;
int j = 0;
for (int i = 0; i < fields.length; ++i) {
// TODO: Check for anonymous bit-fields. ICPPField doesn't currently expose whether
// it's a bit-field.
if (fields[i].isStatic()) {
if (fields == result) {
result = new ICPPField[fields.length - 1];
System.arraycopy(fields, 0, result, 0, i);
}
} else if (fields != result) {
result[j] = fields[i];
++j;
}
}
return ArrayUtil.trim(result);
}
}

View file

@ -49,7 +49,6 @@ import org.eclipse.cdt.core.dom.ast.cpp.ICPPBasicType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPConstructor;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPEnumeration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPField;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
@ -58,7 +57,6 @@ import org.eclipse.cdt.core.dom.ast.cpp.ICPPPointerToMemberType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPReferenceType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateArgument;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateInstance;
import org.eclipse.cdt.core.parser.util.ArrayUtil;
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
import org.eclipse.cdt.internal.core.dom.parser.ArithmeticConversion;
import org.eclipse.cdt.internal.core.dom.parser.ITypeContainer;
@ -334,97 +332,6 @@ public class Conversions {
return checkStandardConversionSequence(uqSource, target);
}
/**
* Get those fields of 'targetClass' which participate in aggregate initialization.
* These are the declared fields, excluding static fields and anonymous bit-fields
* ([decl.init.aggr] p6).
*/
static ICPPField[] getFieldsForAggregateInitialization(ICPPClassType targetClass) {
ICPPField[] fields = targetClass.getDeclaredFields();
ICPPField[] result = fields;
int j = 0;
for (int i = 0; i < fields.length; ++i) {
// TODO: Check for anonymous bit-fields. ICPPField doesn't currently expose whether
// it's a bit-field.
if (fields[i].isStatic()) {
if (fields == result) {
result = new ICPPField[fields.length - 1];
System.arraycopy(fields, 0, result, 0, i);
}
} else if (fields != result) {
result[j] = fields[i];
++j;
}
}
return ArrayUtil.trim(result);
}
/**
* Checks whether 'targetClass' can be initialized from 'list' according to the rules for
* aggregate initialization ([dcl.init.aggr]).
*/
static boolean checkAggregateInitialization(EvalInitList list, ICPPClassType targetClass) throws DOMException {
ICPPField[] fields = getFieldsForAggregateInitialization(targetClass);
ICPPEvaluation[] initializers = list.getClauses();
// p7: An initializer-list is ill-formed if the number of initializer-clauses exceeds
// the number of members to initialize.
if (initializers.length > fields.length) {
return false;
}
// p3: The elements of the initializer list are taken as initializers for the elements
// of the aggregate, in order.
int i = 0;
for (; i < initializers.length; ++i) {
ICPPEvaluation initializer = initializers[i];
ICPPField field = fields[i];
// Each element is copy-initialized from the corresponding initializer-clause.
Cost cost = checkImplicitConversionSequence(field.getType(), initializer.getType(),
initializer.getValueCategory(), UDCMode.ALLOWED, Context.ORDINARY);
if (!cost.converts()) {
return false;
}
// If the initializer-clause is an expression and a narrowing conversion is
// required to convert the expression, the program is ill-formed.
if (!(initializer instanceof EvalInitList) && cost.isNarrowingConversion()) {
return false;
}
}
// p8: If there are fewer initializer-clauses than there are elements in the
// aggregate, then each element not explicitly initialized shall be
// initialized from its default member initializer or, if there is no
// default member initializer, from an empty initializer list.
for (; i < fields.length; ++i) {
ICPPField field = fields[i];
IValue initialValue = field.getInitialValue();
if (initialValue != null) {
continue; // has a default member initializer
}
// p11: If an incomplete or empty initializer-list leaves a member of
// reference type uninitialized, the program is ill-formed.
IType fieldType = SemanticUtil.getNestedType(field.getType(), TDEF);
if (fieldType instanceof ICPPReferenceType) {
return false;
}
// Empty initializer list
EvalInitList emptyInit = new EvalInitList(ICPPEvaluation.EMPTY_ARRAY, CPPSemantics.getCurrentLookupPoint());
Cost cost = listInitializationSequence(emptyInit, fieldType, UDCMode.ALLOWED, false);
if (!cost.converts()) {
return false;
}
}
// TODO: Implement brace elision rules.
return true;
}
/**
* 13.3.3.1.5 List-initialization sequence [over.ics.list]
*/
@ -438,22 +345,9 @@ public class Conversions {
static Cost listInitializationSequenceHelper(EvalInitList arg, IType target, UDCMode udc, boolean isDirect)
throws DOMException {
IType listType = getInitListType(target);
if (listType == null && target instanceof IArrayType) {
Number arraySize = ((IArrayType) target).getSize().numberValue();
if (arraySize != null) {
IType elementType = ((IArrayType) target).getType();
// TODO(nathanridge): If there are fewer initializer clauses than the array size,
// then the element type is required to be default-constructible.
if (arg.getClauses().length <= arraySize.longValue()) {
listType = elementType;
}
}
}
if (listType != null) {
ICPPEvaluation[] clauses = arg.getClauses();
Cost worstCost = new Cost(arg.getType(), target, Rank.IDENTITY);
for (ICPPEvaluation clause : clauses) {
for (ICPPEvaluation clause : arg.getClauses()) {
Cost cost = checkImplicitConversionSequence(listType, clause.getType(), clause.getValueCategory(),
UDCMode.ALLOWED, Context.ORDINARY);
if (!cost.converts())
@ -467,6 +361,8 @@ public class Conversions {
}
}
return worstCost;
} else if (target instanceof IArrayType) {
return AggregateInitialization.check(target, arg);
}
IType noCVTarget = getNestedType(target, CVTYPE | TDEF);
@ -475,10 +371,12 @@ public class Conversions {
return Cost.NO_CONVERSION;
ICPPClassType classTarget = (ICPPClassType) noCVTarget;
if (TypeTraits.isAggregateClass(classTarget) && checkAggregateInitialization(arg, classTarget)) {
Cost cost = new Cost(arg.getType(), target, Rank.IDENTITY);
cost.setUserDefinedConversion(null);
return cost;
if (TypeTraits.isAggregateClass(classTarget)) {
Cost cost = AggregateInitialization.check(classTarget, arg);
if (cost.converts()) {
cost.setUserDefinedConversion(null);
return cost;
}
}
return listInitializationOfClass(arg, classTarget, isDirect, udc == UDCMode.DEFER);
}

View file

@ -272,7 +272,7 @@ public class EvalTypeId extends CPPDependentEvaluation {
ICPPClassType classType = (ICPPClassType) simplifiedType;
ICPPEvaluation[] arguments = fArguments;
ICPPConstructor[] constructors = classType.getConstructors();
if (arguments.length == 1 && arguments[0] instanceof EvalInitList) {
if (arguments.length == 1 && arguments[0] instanceof EvalInitList && !fUsesBracedInitList) {
// List-initialization of a class (dcl.init.list-3).
if (TypeTraits.isAggregateClass(classType)) {
// Pretend that aggregate initialization is calling the default constructor.
@ -299,10 +299,18 @@ public class EvalTypeId extends CPPDependentEvaluation {
if (binding instanceof ICPPFunction) {
return (ICPPFunction) binding;
}
// TODO check aggregate initialization
if (fUsesBracedInitList && allConstructorsAreCompilerGenerated(constructors)) {
return AGGREGATE_INITIALIZATION;
}
if (binding instanceof IProblemBinding && !(binding instanceof ICPPFunction))
return new CPPFunction.CPPFunctionProblem(null, ((IProblemBinding) binding).getID(),
classType.getNameCharArray());
} catch (DOMException e) {
CCorePlugin.log(e);
}
// TODO check aggregate initialization
if (fUsesBracedInitList && allConstructorsAreCompilerGenerated(constructors)) {
return AGGREGATE_INITIALIZATION;
}