Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11781,4 +11781,116 @@ public void testRecognizeConstructorWithSemicolonAfterBody() throws Exception {
public void testAllowAggregateInitializationInTemplateBody() throws Exception {
parseAndCheckImplicitNameBindings();
}

// // pattern from gdb custom allocator
// template<typename T>
// struct ConversionTraits {
// template<typename U>
// struct ConverterImpl {
// static constexpr int value = 42;
// };
// };
// template<typename T>
// struct Derived : public T {
// using T::T; // does not introduce name T, pulls constructors of T
// template<typename U>
// struct Converter {
// typedef ConversionTraits<T> traits;
// typedef typename traits::template ConverterImpl<U> conversion;
// };
// };
//
// template<typename T> class In {};
// template<typename T> class Out {};
//
// static constexpr auto conversion_value
// = Derived<In<int>>::Converter<Out<double>>::conversion::value;
public void testUsingDeclaratorPullDependentBaseConstructors() throws Exception {
parseAndCheckImplicitNameBindings();
BindingAssertionHelper helper = getAssertionHelper();
helper.assertVariableValue("conversion_value", 42);
}

// template<typename T>
// struct ConversionTraits {
// template<typename U>
// struct ConverterImpl {
// static constexpr int value = 42;
// };
// };
//
// template<int I, typename T>
// struct Derived : public Derived<I - 1, T> {
// using Base = Derived<I - 1, T>;
// using Base::Base; // does not introduce name Base, pulls constructors of Derived<I - 1, T>
// };
//
// template<typename T>
// struct Derived<-1, T> {
// template<typename U>
// struct Converter {
// typedef ConversionTraits<T> traits;
// typedef typename traits::template ConverterImpl<U> conversion;
// };
// };
//
// template<typename T> class In {};
// template<typename T> class Out {};
//
// static constexpr auto conversion_value
// = Derived<17, In<int>>::Converter<Out<double>>::conversion::value;
//
// static_assert(conversion_value == 42);
public void testUsingDeclaratorPullDependentTemplateBaseConstructors() throws Exception {
parseAndCheckImplicitNameBindings();
BindingAssertionHelper helper = getAssertionHelper();
helper.assertVariableValue("conversion_value", 42);
}

// template<typename T>
// struct ConversionTraits {
// template<typename U>
// struct ConverterImpl {
// static constexpr int value = 42;
// };
// };
//
// template<typename T>
// class DerivedInstantiator;
//
// template<int I, typename T>
// struct Derived : public Derived<I - 1, T> {
// using Base = Derived<I - 1, T>;
// using Base::Base; // does not introduce name Derived, pulls constructors of Derived<I - 1, T>
// };
//
// template<typename T>
// struct Derived<-1, DerivedInstantiator<T>> {
// template<typename U>
// struct Converter {
// typedef ConversionTraits<T> traits;
// typedef typename traits::template ConverterImpl<U> conversion;
// };
// };
//
// static constexpr int DEPTH = 17;
//
// template<typename T>
// struct DerivedInstantiator : public Derived<DEPTH, DerivedInstantiator<T>> {
// using Derived = Derived<DEPTH, DerivedInstantiator>; // name Derived now means type of direct base class, not Derived template class
// using Derived::Derived; // does not introduce name Derived, pulls constructors of Derived<I - 1, T>
// };
//
// template<typename T> class In {};
// template<typename T> class Out {};
//
// static constexpr auto conversion_value
// = DerivedInstantiator<In<int>>::Converter<Out<double>>::conversion::value;
//
// static_assert(conversion_value == 42);
public void testUsingDeclaratorPullRenamedDependentTemplateBaseConstructors() throws Exception {
parseAndCheckImplicitNameBindings();
BindingAssertionHelper helper = getAssertionHelper();
helper.assertVariableValue("conversion_value", 42);
}
}
2 changes: 1 addition & 1 deletion core/org.eclipse.cdt.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.cdt.core; singleton:=true
Bundle-Version: 9.2.100.qualifier
Bundle-Version: 9.2.200.qualifier
Bundle-Activator: org.eclipse.cdt.core.CCorePlugin
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright (c) 2025 Igor V. Kovalenko.
*
* 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
*
* Contributors:
* Igor V. Kovalenko - initial implementation
*******************************************************************************/
package org.eclipse.cdt.internal.core.dom.parser.cpp;

import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.internal.core.dom.parser.ASTAmbiguousNode;

public class CPPASTAmbiguousUsingDeclaration extends ASTAmbiguousNode implements IASTDeclaration {
private final IASTDeclaration fDeclaration;

public CPPASTAmbiguousUsingDeclaration(IASTDeclaration declaration) {
fDeclaration = declaration;
}

@Override
public IASTDeclaration copy() {
throw new UnsupportedOperationException();
}

@Override
public IASTDeclaration copy(CopyStyle style) {
throw new UnsupportedOperationException();
}

@Override
public IASTNode[] getNodes() {
return new IASTNode[] { fDeclaration };
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2004, 2015 IBM Corporation and others.
* Copyright (c) 2004, 2015, 2025 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand All @@ -21,9 +21,13 @@
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTClassVirtSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNameSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDeclaration;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisibilityLabel;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassScope;
import org.eclipse.cdt.core.parser.util.ArrayUtil;
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
import org.eclipse.cdt.internal.core.dom.parser.ASTQueries;

/**
Expand Down Expand Up @@ -154,6 +158,49 @@ public void addMemberDeclaration(IASTDeclaration decl) {

@Override
public final void addDeclaration(IASTDeclaration decl) {
// [C++20] 6.5.4.2 Class members [class.qual]
// (2) In a lookup in which function names are not ignored
// and the nested-name-specifier nominates a class C:
// (2.1) if the name specified after the nested-name-specifier,
// when looked up in C, is the injected-class-name of C (11.1), or
// (2.2) in a using-declarator of a using-declaration (9.9) that is a member-declaration,
// if the name specified after the nested-name-specifier is the same as the
// identifier or the simple-template-id's template-name in the last component
// of the nested-name-specifier,
// the name is instead considered to name the constructor of class C.
// ...
// Such a constructor name shall be used only in the declarator-id of
// a declaration that names a constructor or in a using-declaration.
//
// AST2TemplateTests.testInheritedConstructor_489710() has the following code fragment:
// template <int I, typename T>
// struct B : public B<I - 1, T> {
// ...
// using Base = B<I - 1, T>;
// using Base::Base;
// ...
// };
//
// Here, while resolving ambiguities finds CPPASTAmbiguousTemplateArgument of first member 'using Base = B<I - 1, T>'
// declaration name lookup for 'I' in 'struct B' scope causes cache population of CPPClassScope of 'struct B'.
// Cache population finds second member 'using Base::Base' declaration but at that point it is not possible
// to tell whether this member using-declaration nominates a class because first using declaration still contains
// unresolved ambiguous template argument. The needed typedef is found but mapped type is deferred class
// which yields ProblemBinding.
//
// To fix this detect if using-declaration potentially names a constructor and return CPPASTAmbiguousUsingDeclaration
// wrapping real declaration. This will be skipped while populating 'struct B' class scope and resolved after
// first 'using Base = B<...>' member declaration ambiguities are resolved. Cache will be populated after resolution is complete.

if (decl instanceof ICPPASTUsingDeclaration using && using.getName() instanceof ICPPASTQualifiedName qName) {
final ICPPASTNameSpecifier[] qualifier = qName.getQualifier();
if (qualifier.length > 0 && qualifier[qualifier.length - 1] instanceof IASTName segmentName) {
if (CharArrayUtils.equals(segmentName.getSimpleID(), qName.getLastName().getSimpleID())) {
decl = new CPPASTAmbiguousUsingDeclaration(decl);
}
}
}

addMemberDeclaration(decl);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2004, 2015 IBM Corporation and others.
* Copyright (c) 2004, 2015, 2025 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand All @@ -25,13 +25,11 @@
import static org.eclipse.cdt.internal.core.dom.parser.cpp.CPPBasicType.VOID;
import static org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil.TDEF;

import java.util.Arrays;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.IName;
import org.eclipse.cdt.core.dom.ast.EScopeKind;
import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFieldReference;
import org.eclipse.cdt.core.dom.ast.IASTName;
Expand All @@ -43,7 +41,6 @@
import org.eclipse.cdt.core.dom.ast.IScope;
import org.eclipse.cdt.core.dom.ast.ISemanticProblem;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNameSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNewExpression;
Expand Down Expand Up @@ -146,58 +143,6 @@ public void createImplicitMembers() {
implicits[i++] = m;
addBinding(m);
}

markInheritedConstructorsSourceBases(compTypeSpec);
}

/**
* Marks bases that serve as sources of inherited constructors.
*/
private void markInheritedConstructorsSourceBases(ICPPASTCompositeTypeSpecifier compositeTypeSpec) {
ICPPBase[] bases = getClassType().getBases();
if (bases.length == 0)
return;
IASTDeclaration[] members = compositeTypeSpec.getMembers();
for (IASTDeclaration member : members) {
if (member instanceof ICPPASTUsingDeclaration) {
IASTName name = ((ICPPASTUsingDeclaration) member).getName();
if (!(name instanceof ICPPASTQualifiedName))
continue;
ICPPASTQualifiedName qName = (ICPPASTQualifiedName) name;
ICPPASTNameSpecifier[] qualifier = qName.getQualifier();
if (qualifier.length == 0)
continue;
IBinding parent = qualifier[qualifier.length - 1].resolveBinding();
if (!(parent instanceof IType) || parent instanceof IProblemBinding)
continue;
if (isConstructorNameForType(qName.getLastName().getSimpleID(), (IType) parent)) {
IType type = SemanticUtil.getNestedType((IType) parent, TDEF);
for (ICPPBase base : bases) {
IType baseClass = base.getBaseClassType();
if (type.isSameType(baseClass)) {
if (base instanceof CPPBaseClause) {
((CPPBaseClause) base).setInheritedConstructorsSource(true);
} else {
CCorePlugin.log(IStatus.ERROR, "Unexpected type of base (" //$NON-NLS-1$
+ base.getClass().getSimpleName() + ") for '" //$NON-NLS-1$
+ compositeTypeSpec.getRawSignature() + "'"); //$NON-NLS-1$
}
}
}
}
}
}
}

private static boolean isConstructorNameForType(char[] lastName, IType type) {
while (type instanceof IBinding) {
if (Arrays.equals(((IBinding) type).getNameCharArray(), lastName))
return true;
if (!(type instanceof ITypedef))
break;
type = ((ITypedef) type).getType();
}
return false;
}

@Override
Expand Down Expand Up @@ -255,7 +200,65 @@ public void addName(IASTName name, boolean adlOnly) {
addConstructor(name);
return;
}
} else if (parent.getParent() instanceof ICPPASTUsingDeclaration usingDeclaration
&& parent instanceof ICPPASTQualifiedName qName) {

// In addition to normal class scope population procedure this method will be called after
// ambiguity resolution for CPPASTAmbiguousUsingDeclaration to handle nominated base class constructors.

// [C++20] 9.9 The using declaration [namespace.udecl]
// 3) In a using-declaration used as a member-declaration, each using-declarator shall either
// name an enumerator or have a nested-name-specifier naming a base class of the class being defined.
// If a using-declarator names a constructor, its nested-name-specifier shall name a direct base class
// of the class being defined

// [C++20] 6.5.4.2 Class members [class.qual]
// (2) In a lookup in which function names are not ignored
// and the nested-name-specifier nominates a class C:
// (2.1) if the name specified after the nested-name-specifier,
// when looked up in C, is the injected-class-name of C (11.1), or
// (2.2) in a using-declarator of a using-declaration (9.9) that is a member-declaration,
// if the name specified after the nested-name-specifier is the same as the
// identifier or the simple-template-id's template-name in the last component
// of the nested-name-specifier,
// the name is instead considered to name the constructor of class C.
// ...
// Such a constructor name shall be used only in the declarator-id of
// a declaration that names a constructor or in a using-declaration.

final ICPPASTNameSpecifier[] qualifier = qName.getQualifier();
final char[] lastName = name.getSimpleID();

if (usingDeclaration.getPropertyInParent() == IASTCompositeTypeSpecifier.MEMBER_DECLARATION
&& qualifier.length > 0 && qualifier[qualifier.length - 1] instanceof IASTName lastSegmentName
&& CharArrayUtils.equals(lastSegmentName.getSimpleID(), lastName)) {
ICPPBase[] bases = getClassType().getBases();
if (bases.length > 0) {
IBinding nominatedBinding = lastSegmentName.resolveBinding();
if (nominatedBinding instanceof IType nominatedType
&& !(nominatedType instanceof IProblemBinding)) {
IType type = SemanticUtil.getNestedType(nominatedType, TDEF);
for (ICPPBase base : bases) {
IType baseClass = base.getBaseClassType();
if (type.isSameType(baseClass)) {
// Member using-declaration names direct base class constructor
// Mark nominated base class as inherited constructors source and skip adding to scope
if (base instanceof CPPBaseClause baseClause) {
baseClause.setInheritedConstructorsSource(true);
} else {
ICPPASTCompositeTypeSpecifier compTypeSpec = (ICPPASTCompositeTypeSpecifier) getPhysicalNode();
CCorePlugin.log(IStatus.ERROR, "Unexpected type of base (" //$NON-NLS-1$
+ base.getClass().getSimpleName() + ") for '" //$NON-NLS-1$
+ compTypeSpec.getRawSignature() + "'"); //$NON-NLS-1$
}
return;
}
}
}
}
}
}

super.addName(name, adlOnly);
}

Expand Down
Loading