diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2TemplateTests.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2TemplateTests.java index c33aabfdd9a..b45b4a06068 100644 --- a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2TemplateTests.java +++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2TemplateTests.java @@ -11781,4 +11781,116 @@ public void testRecognizeConstructorWithSemicolonAfterBody() throws Exception { public void testAllowAggregateInitializationInTemplateBody() throws Exception { parseAndCheckImplicitNameBindings(); } + + // // pattern from gdb custom allocator + // template + // struct ConversionTraits { + // template + // struct ConverterImpl { + // static constexpr int value = 42; + // }; + // }; + // template + // struct Derived : public T { + // using T::T; // does not introduce name T, pulls constructors of T + // template + // struct Converter { + // typedef ConversionTraits traits; + // typedef typename traits::template ConverterImpl conversion; + // }; + // }; + // + // template class In {}; + // template class Out {}; + // + // static constexpr auto conversion_value + // = Derived>::Converter>::conversion::value; + public void testUsingDeclaratorPullDependentBaseConstructors() throws Exception { + parseAndCheckImplicitNameBindings(); + BindingAssertionHelper helper = getAssertionHelper(); + helper.assertVariableValue("conversion_value", 42); + } + + // template + // struct ConversionTraits { + // template + // struct ConverterImpl { + // static constexpr int value = 42; + // }; + // }; + // + // template + // struct Derived : public Derived { + // using Base = Derived; + // using Base::Base; // does not introduce name Base, pulls constructors of Derived + // }; + // + // template + // struct Derived<-1, T> { + // template + // struct Converter { + // typedef ConversionTraits traits; + // typedef typename traits::template ConverterImpl conversion; + // }; + // }; + // + // template class In {}; + // template class Out {}; + // + // static constexpr auto conversion_value + // = Derived<17, In>::Converter>::conversion::value; + // + // static_assert(conversion_value == 42); + public void testUsingDeclaratorPullDependentTemplateBaseConstructors() throws Exception { + parseAndCheckImplicitNameBindings(); + BindingAssertionHelper helper = getAssertionHelper(); + helper.assertVariableValue("conversion_value", 42); + } + + // template + // struct ConversionTraits { + // template + // struct ConverterImpl { + // static constexpr int value = 42; + // }; + // }; + // + // template + // class DerivedInstantiator; + // + // template + // struct Derived : public Derived { + // using Base = Derived; + // using Base::Base; // does not introduce name Derived, pulls constructors of Derived + // }; + // + // template + // struct Derived<-1, DerivedInstantiator> { + // template + // struct Converter { + // typedef ConversionTraits traits; + // typedef typename traits::template ConverterImpl conversion; + // }; + // }; + // + // static constexpr int DEPTH = 17; + // + // template + // struct DerivedInstantiator : public Derived> { + // using Derived = Derived; // 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 + // }; + // + // template class In {}; + // template class Out {}; + // + // static constexpr auto conversion_value + // = DerivedInstantiator>::Converter>::conversion::value; + // + // static_assert(conversion_value == 42); + public void testUsingDeclaratorPullRenamedDependentTemplateBaseConstructors() throws Exception { + parseAndCheckImplicitNameBindings(); + BindingAssertionHelper helper = getAssertionHelper(); + helper.assertVariableValue("conversion_value", 42); + } } diff --git a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF index 2b120b41cd1..85c9f459b9f 100644 --- a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF +++ b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF @@ -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 diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPASTAmbiguousUsingDeclaration.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPASTAmbiguousUsingDeclaration.java new file mode 100644 index 00000000000..ea982dacbcf --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPASTAmbiguousUsingDeclaration.java @@ -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 }; + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPASTCompositeTypeSpecifier.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPASTCompositeTypeSpecifier.java index 78c45155f31..d5cf664e3f2 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPASTCompositeTypeSpecifier.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPASTCompositeTypeSpecifier.java @@ -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 @@ -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; /** @@ -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 + // struct B : public B { + // ... + // using Base = B; + // using Base::Base; + // ... + // }; + // + // Here, while resolving ambiguities finds CPPASTAmbiguousTemplateArgument of first member 'using Base = B' + // 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); } diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPClassScope.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPClassScope.java index 2aaf8ad52da..37defb40a46 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPClassScope.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPClassScope.java @@ -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 @@ -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; @@ -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; @@ -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 @@ -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); }