Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Clang] Call to lambda expressions in unevaluated context is improperly rejected by Clang #81145

Closed
zyn0217 opened this issue Feb 8, 2024 · 5 comments · Fixed by #82310
Closed
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" lambda C++11 lambda expressions rejects-valid

Comments

@zyn0217
Copy link
Contributor

zyn0217 commented Feb 8, 2024

I'm not confident that I'm summarizing the issue accurately, but here is an example of the case that I discovered while at #70601.

template <class T>
struct S {
  template <class U>
  using Type = decltype([]<class V> {
    return V();
  }.template operator()<U>());
};

S<int>::Type<int> V;

Clang currently rejects the above, complaining that there are no viable functions, which I think is suspicious.

<source>:6:14: error: no matching member function for call to 'operator()'
    4 |   using Type = decltype([]<class V> {
      |                         ~~~~~~~~~~~~~
    5 |     return V();
      |     ~~~~~~~~~~~
    6 |   }.template operator()<U>());
      |   ~~~~~~~~~~~^~~~~~~~~~~~~
<source>:9:9: note: in instantiation of template type alias 'Type' requested here
    9 | S<int>::Type<int> V;
      |         ^
<source>:4:25: note: candidate function template not viable: no known conversion from '(lambda at <source>:4:25)' to 'const S<int>::(lambda at <source>:4:25)' for object argument
    4 |   using Type = decltype([]<class V> {
      |                         ^
1 error generated.

The example is also on godbolt, and we can see that this is accepted by both MSVC and GCC.

@zyn0217 zyn0217 added clang:frontend Language frontend issues, e.g. anything involving "Sema" rejects-valid labels Feb 8, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Feb 8, 2024

@llvm/issue-subscribers-clang-frontend

Author: Younan Zhang (zyn0217)

I'm not confident that I'm summarizing the issue accurately, but here is an example of the case that I discovered while at https://github.com//issues/70601. ```cpp template <class T> struct S { template <class U> using Type = decltype([]<class V> { return V(); }.template operator()<U>()); };

S<int>::Type<int> V;


Clang currently rejects the above, complaining that there are no viable functions, which I think is suspicious.

```txt
&lt;source&gt;:6:14: error: no matching member function for call to 'operator()'
    4 |   using Type = decltype([]&lt;class V&gt; {
      |                         ~~~~~~~~~~~~~
    5 |     return V();
      |     ~~~~~~~~~~~
    6 |   }.template operator()&lt;U&gt;());
      |   ~~~~~~~~~~~^~~~~~~~~~~~~
&lt;source&gt;:9:9: note: in instantiation of template type alias 'Type' requested here
    9 | S&lt;int&gt;::Type&lt;int&gt; V;
      |         ^
&lt;source&gt;:4:25: note: candidate function template not viable: no known conversion from '(lambda at &lt;source&gt;:4:25)' to 'const S&lt;int&gt;::(lambda at &lt;source&gt;:4:25)' for object argument
    4 |   using Type = decltype([]&lt;class V&gt; {
      |                         ^
1 error generated.

The example is also on godbolt, and we can see that this is accepted by both MSVC and GCC.

@zyn0217
Copy link
Contributor Author

zyn0217 commented Feb 8, 2024

I've traced the error and found that the issue occurs when we finish the template argument deduction. At this point, we're in the callback of FinishTemplateArgumentDeduction, which eventually calls Sema::CheckNonDependentConversions.

We determine whether the argument type can be converted to the parameter type through some conversion sequences. Finally, we fail at this line in TryObjectArgumentInitialization. Looking around it, we can see that ClassTypeCanon and FromTypeCanon having different values, even though they both should point to the same Lambda type.

// Check that we have either the same type or a derived type. It
// affects the conversion rank.
QualType ClassTypeCanon = S.Context.getCanonicalType(ClassType);
ImplicitConversionKind SecondKind;
if (ClassTypeCanon == FromTypeCanon.getLocalUnqualifiedType()) {
SecondKind = ICK_Identity;
} else if (S.IsDerivedFrom(Loc, FromType, ClassType)) {
SecondKind = ICK_Derived_To_Base;
} else if (!Method->isExplicitObjectMemberFunction()) {
ICS.setBad(BadConversionSequence::unrelated_class,
FromType, ImplicitParamType);
return ICS;
}

These two different types ought to correspond to the Lambda type before and after the TransformLambdaExpr. Looking back at the call stack, I found that both the ClassType and FromType stem from a CallExpr that has been transformed, and the callee of this CallExpr was an UnresolvedMemberExpr before the transformation. Following this clue, I entered TransformUnresolvedMemberExpr and found a suspicious point:

template <typename Derived>
ExprResult TreeTransform<Derived>::TransformUnresolvedMemberExpr(
UnresolvedMemberExpr *Old) {
// Transform the base of the expression.
ExprResult Base((Expr *)nullptr);
QualType BaseType;
if (!Old->isImplicitAccess()) {
Base = getDerived().TransformExpr(Old->getBase());
if (Base.isInvalid())
return ExprError();
Base =
getSema().PerformMemberExprBaseConversion(Base.get(), Old->isArrow());
if (Base.isInvalid())
return ExprError();
BaseType = Base.get()->getType();
} else {
BaseType = getDerived().TransformType(Old->getBaseType());
}
NestedNameSpecifierLoc QualifierLoc;
if (Old->getQualifierLoc()) {
QualifierLoc =
getDerived().TransformNestedNameSpecifierLoc(Old->getQualifierLoc());
if (!QualifierLoc)
return ExprError();
}
SourceLocation TemplateKWLoc = Old->getTemplateKeywordLoc();
LookupResult R(SemaRef, Old->getMemberNameInfo(), Sema::LookupOrdinaryName);
// Transform the declaration set.
if (TransformOverloadExprDecls(Old, /*RequiresADL*/ false, R))
return ExprError();

There, we first try to convert the "BaseType", which is exactly this LambdaExpr. After that, we get a new CXXRecordDecl, along with an attached, transformed FunctionTemplateDecl. Then, we try to convert the DeclarationSet of this UnresolvedMemberExpr, where we eventually bake the untransformed FunctionTemplateDecl with the transformed CXXRecordType into a new UnresolvedMemberExpr.

This is the discrepancy that causes the problem above, where ClassTypeCanon actually comes from the DeclContext of the old FunctionTemplateDecl, which is the Lambda type before the transformation; while FromTypeCanon is the transformed CXXRecordType.

I've spent some hours fixing this, but I have no luck yet. The approach that makes sense to me is to have TransformUnresolvedMemberExpr get the correct DeclarationSet. This requires us to update TemplateInstantiator::TransformDecl, which is implemented by looking for instantiatedDecl in a LocalInstantiationScope. This raises another problem, as when we transform the LambdaExpr, we actually set up a distinct LocalInstantiationScope, which would be destroyed before we handle the UnresolvedMemberExpr.

ExprResult TransformLambdaExpr(LambdaExpr *E) {
LocalInstantiationScope Scope(SemaRef, /*CombineWithOuterScope=*/true);
Sema::ConstraintEvalRAII<TemplateInstantiator> RAII(*this);
ExprResult Result = inherited::TransformLambdaExpr(E);
if (Result.isInvalid())
return Result;

I tried to circumvent that by hoisting the LocalInstantiationScope into the TransformUnresolvedMemberExpr. The error then disappears, but unfortunately it brings me some regressions. I have no idea how to fix that so far, so I really appreciate it if someone could give me guidance!

Here is what I've done locally. #81150

CC @cor3ntin @erichkeane @AaronBallman

@MagentaTreehouse
Copy link
Contributor

Possibly related issue: #76674

@EugeneZelenko EugeneZelenko added the lambda C++11 lambda expressions label Feb 18, 2024
@jcsxky
Copy link
Contributor

jcsxky commented Feb 19, 2024

Possibly related issue: #76674

Not really. Root case of these two issues are different. Skip transformation causes #76674 and I have a patch to fix it(but i haven't merge into main since it may not an perfect fix and can't fix other related issues)

@zyn0217
Copy link
Contributor Author

zyn0217 commented Feb 20, 2024

Not really. Root case of these two issues are different. Skip transformation causes #76674 and I have a patch to fix it(but i haven't merge into main since it may not an perfect fix and can't fix other related issues)

Oh, I have submitted a PR for these: #82310.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" lambda C++11 lambda expressions rejects-valid
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

5 participants