-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[CIR] Add support for lambda expressions #157751
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
Conversation
This adds support for lambda operators and lambda calls. This does not include support for static lambda invoke, which will be added in a later change.
|
@llvm/pr-subscribers-clangir @llvm/pr-subscribers-clang Author: Andy Kaylor (andykaylor) ChangesThis adds support for lambda operators and lambda calls. This does not include support for static lambda invoke, which will be added in a later change. Patch is 43.48 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/157751.diff 10 Files Affected:
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 34ee0b8b29a0c..ee23b87b9bfb5 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2267,6 +2267,10 @@ def CIR_FuncOp : CIR_Op<"func", [
The function linkage information is specified by `linkage`, as defined by
`GlobalLinkageKind` attribute.
+ The `lambda` translates to a C++ `operator()` that implements a lambda, this
+ allow callsites to make certain assumptions about the real function nature
+ when writing analysis.
+
The `no_proto` keyword is used to identify functions that were declared
without a prototype and, consequently, may contain calls with invalid
arguments and undefined behavior.
@@ -2289,6 +2293,7 @@ def CIR_FuncOp : CIR_Op<"func", [
let arguments = (ins SymbolNameAttr:$sym_name,
CIR_VisibilityAttr:$global_visibility,
TypeAttrOf<CIR_FuncType>:$function_type,
+ UnitAttr:$lambda,
UnitAttr:$no_proto,
UnitAttr:$dso_local,
DefaultValuedAttr<CIR_GlobalLinkageKind,
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 52d5f8a2ded2c..a44671be01ad0 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -188,6 +188,7 @@ struct MissingFeatures {
static bool builtinCallF128() { return false; }
static bool builtinCallMathErrno() { return false; }
static bool builtinCheckKind() { return false; }
+ static bool cgCapturedStmtInfo() { return false; }
static bool cgFPOptionsRAII() { return false; }
static bool cirgenABIInfo() { return false; }
static bool cleanupAfterErrorDiags() { return false; }
@@ -234,7 +235,6 @@ struct MissingFeatures {
static bool isMemcpyEquivalentSpecialMember() { return false; }
static bool isTrivialCtorOrDtor() { return false; }
static bool lambdaCaptures() { return false; }
- static bool lambdaFieldToName() { return false; }
static bool loopInfoStack() { return false; }
static bool lowerAggregateLoadStore() { return false; }
static bool lowerModeOptLevel() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 0a8dc2b62fe21..f66aa4d39adff 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -820,7 +820,7 @@ mlir::Value CIRGenFunction::getVTTParameter(GlobalDecl gd, bool forVirtualBase,
if (!cgm.getCXXABI().needsVTTParameter(gd))
return nullptr;
- const CXXRecordDecl *rd = cast<CXXMethodDecl>(curFuncDecl)->getParent();
+ const CXXRecordDecl *rd = cast<CXXMethodDecl>(curCodeDecl)->getParent();
const CXXRecordDecl *base = cast<CXXMethodDecl>(gd.getDecl())->getParent();
uint64_t subVTTIndex;
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index aab7e2745f30f..9145b50f33d35 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -461,7 +461,8 @@ LValue CIRGenFunction::emitLValueForField(LValue base, const FieldDecl *field) {
llvm::StringRef fieldName = field->getName();
unsigned fieldIndex;
- assert(!cir::MissingFeatures::lambdaFieldToName());
+ if (cgm.lambdaFieldToName.count(field))
+ fieldName = cgm.lambdaFieldToName[field];
if (rec->isUnion())
fieldIndex = field->getFieldIndex();
@@ -476,8 +477,16 @@ LValue CIRGenFunction::emitLValueForField(LValue base, const FieldDecl *field) {
// If this is a reference field, load the reference right now.
if (fieldType->isReferenceType()) {
- cgm.errorNYI(field->getSourceRange(), "emitLValueForField: reference type");
- return LValue();
+ assert(!cir::MissingFeatures::opTBAA());
+ LValue refLVal = makeAddrLValue(addr, fieldType, fieldBaseInfo);
+ if (recordCVR & Qualifiers::Volatile)
+ refLVal.getQuals().addVolatile();
+ addr = emitLoadOfReference(refLVal, getLoc(field->getSourceRange()),
+ &fieldBaseInfo);
+
+ // Qualifiers on the struct don't apply to the referencee.
+ recordCVR = 0;
+ fieldType = fieldType->getPointeeType();
}
if (field->hasAttr<AnnotateAttr>()) {
@@ -619,6 +628,38 @@ static cir::FuncOp emitFunctionDeclPointer(CIRGenModule &cgm, GlobalDecl gd) {
return cgm.getAddrOfFunction(gd);
}
+static LValue emitCapturedFieldLValue(CIRGenFunction &cgf, const FieldDecl *fd,
+ mlir::Value thisValue) {
+ return cgf.emitLValueForLambdaField(fd, thisValue);
+}
+
+/// Given that we are currently emitting a lambda, emit an l-value for
+/// one of its members.
+///
+LValue CIRGenFunction::emitLValueForLambdaField(const FieldDecl *field,
+ mlir::Value thisValue) {
+ bool hasExplicitObjectParameter = false;
+ const auto *methD = dyn_cast_if_present<CXXMethodDecl>(curCodeDecl);
+ LValue lambdaLV;
+ if (methD) {
+ hasExplicitObjectParameter = methD->isExplicitObjectMemberFunction();
+ assert(methD->getParent()->isLambda());
+ assert(methD->getParent() == field->getParent());
+ }
+ if (hasExplicitObjectParameter) {
+ cgm.errorNYI(field->getSourceRange(), "ExplicitObjectMemberFunction");
+ } else {
+ QualType lambdaTagType =
+ getContext().getCanonicalTagType(field->getParent());
+ lambdaLV = makeNaturalAlignAddrLValue(thisValue, lambdaTagType);
+ }
+ return emitLValueForField(lambdaLV, field);
+}
+
+LValue CIRGenFunction::emitLValueForLambdaField(const FieldDecl *field) {
+ return emitLValueForLambdaField(field, cxxabiThisValue);
+}
+
static LValue emitFunctionDeclLValue(CIRGenFunction &cgf, const Expr *e,
GlobalDecl gd) {
const FunctionDecl *fd = cast<FunctionDecl>(gd.getDecl());
@@ -645,6 +686,57 @@ static LValue emitFunctionDeclLValue(CIRGenFunction &cgf, const Expr *e,
AlignmentSource::Decl);
}
+/// Determine whether we can emit a reference to \p vd from the current
+/// context, despite not necessarily having seen an odr-use of the variable in
+/// this context.
+/// TODO(cir): This could be shared with classic codegen.
+static bool canEmitSpuriousReferenceToVariable(CIRGenFunction &cgf,
+ const DeclRefExpr *e,
+ const VarDecl *vd) {
+ // For a variable declared in an enclosing scope, do not emit a spurious
+ // reference even if we have a capture, as that will emit an unwarranted
+ // reference to our capture state, and will likely generate worse code than
+ // emitting a local copy.
+ if (e->refersToEnclosingVariableOrCapture())
+ return false;
+
+ // For a local declaration declared in this function, we can always reference
+ // it even if we don't have an odr-use.
+ if (vd->hasLocalStorage()) {
+ return vd->getDeclContext() ==
+ dyn_cast_or_null<DeclContext>(cgf.curCodeDecl);
+ }
+
+ // For a global declaration, we can emit a reference to it if we know
+ // for sure that we are able to emit a definition of it.
+ vd = vd->getDefinition(cgf.getContext());
+ if (!vd)
+ return false;
+
+ // Don't emit a spurious reference if it might be to a variable that only
+ // exists on a different device / target.
+ // FIXME: This is unnecessarily broad. Check whether this would actually be a
+ // cross-target reference.
+ if (cgf.getLangOpts().OpenMP || cgf.getLangOpts().CUDA ||
+ cgf.getLangOpts().OpenCL) {
+ return false;
+ }
+
+ // We can emit a spurious reference only if the linkage implies that we'll
+ // be emitting a non-interposable symbol that will be retained until link
+ // time.
+ switch (cgf.cgm.getCIRLinkageVarDefinition(vd, /*IsConstant=*/false)) {
+ case cir::GlobalLinkageKind::ExternalLinkage:
+ case cir::GlobalLinkageKind::LinkOnceODRLinkage:
+ case cir::GlobalLinkageKind::WeakODRLinkage:
+ case cir::GlobalLinkageKind::InternalLinkage:
+ case cir::GlobalLinkageKind::PrivateLinkage:
+ return true;
+ default:
+ return false;
+ }
+}
+
LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *e) {
const NamedDecl *nd = e->getDecl();
QualType ty = e->getType();
@@ -652,6 +744,32 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *e) {
assert(e->isNonOdrUse() != NOUR_Unevaluated &&
"should not emit an unevaluated operand");
+ if (const auto *vd = dyn_cast<VarDecl>(nd)) {
+ // Global Named registers access via intrinsics only
+ if (vd->getStorageClass() == SC_Register && vd->hasAttr<AsmLabelAttr>() &&
+ !vd->isLocalVarDecl()) {
+ cgm.errorNYI(e->getSourceRange(),
+ "emitDeclRefLValue: Global Named registers access");
+ return LValue();
+ }
+
+ if (e->isNonOdrUse() == NOUR_Constant &&
+ (vd->getType()->isReferenceType() ||
+ !canEmitSpuriousReferenceToVariable(*this, e, vd))) {
+ cgm.errorNYI(e->getSourceRange(), "emitDeclRefLValue: NonOdrUse");
+ return LValue();
+ }
+
+ // Check for captured variables.
+ if (e->refersToEnclosingVariableOrCapture()) {
+ vd = vd->getCanonicalDecl();
+ if (FieldDecl *fd = lambdaCaptureFields.lookup(vd))
+ return emitCapturedFieldLValue(*this, fd, cxxabiThisValue);
+ assert(!cir::MissingFeatures::cgCapturedStmtInfo());
+ assert(!cir::MissingFeatures::openMP());
+ }
+ }
+
if (const auto *vd = dyn_cast<VarDecl>(nd)) {
// Checks for omitted feature handling
assert(!cir::MissingFeatures::opAllocaStaticLocal());
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
index dc34d2b3baa8d..5615960ea5247 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
@@ -99,6 +99,7 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
assert(!cir::MissingFeatures::aggValueSlotDestructedFlag());
Visit(e->getSubExpr());
}
+ void VisitLambdaExpr(LambdaExpr *e);
// Stubs -- These should be moved up when they are implemented.
void VisitCastExpr(CastExpr *e) {
@@ -239,9 +240,6 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
cgf.cgm.errorNYI(e->getSourceRange(),
"AggExprEmitter: VisitCXXInheritedCtorInitExpr");
}
- void VisitLambdaExpr(LambdaExpr *e) {
- cgf.cgm.errorNYI(e->getSourceRange(), "AggExprEmitter: VisitLambdaExpr");
- }
void VisitCXXStdInitializerListExpr(CXXStdInitializerListExpr *e) {
cgf.cgm.errorNYI(e->getSourceRange(),
"AggExprEmitter: VisitCXXStdInitializerListExpr");
@@ -495,8 +493,10 @@ void AggExprEmitter::emitInitializationToLValue(Expr *e, LValue lv) {
if (isa<NoInitExpr>(e))
return;
- if (type->isReferenceType())
- cgf.cgm.errorNYI("emitInitializationToLValue ReferenceType");
+ if (type->isReferenceType()) {
+ RValue rv = cgf.emitReferenceBindingToExpr(e);
+ return cgf.emitStoreThroughLValue(rv, lv);
+ }
switch (cgf.getEvaluationKind(type)) {
case cir::TEK_Complex:
@@ -550,6 +550,47 @@ void AggExprEmitter::emitNullInitializationToLValue(mlir::Location loc,
cgf.emitNullInitialization(loc, lv.getAddress(), lv.getType());
}
+void AggExprEmitter::VisitLambdaExpr(LambdaExpr *e) {
+ CIRGenFunction::SourceLocRAIIObject loc{cgf, cgf.getLoc(e->getSourceRange())};
+ AggValueSlot slot = ensureSlot(cgf.getLoc(e->getSourceRange()), e->getType());
+ [[maybe_unused]] LValue slotLV =
+ cgf.makeAddrLValue(slot.getAddress(), e->getType());
+
+ // We'll need to enter cleanup scopes in case any of the element
+ // initializers throws an exception or contains branch out of the expressions.
+ assert(!cir::MissingFeatures::opScopeCleanupRegion());
+
+ for (auto [curField, capture, captureInit] : llvm::zip(
+ e->getLambdaClass()->fields(), e->captures(), e->capture_inits())) {
+ // Pick a name for the field.
+ llvm::StringRef fieldName = curField->getName();
+ if (capture.capturesVariable()) {
+ assert(!curField->isBitField() && "lambdas don't have bitfield members!");
+ ValueDecl *v = capture.getCapturedVar();
+ fieldName = v->getName();
+ cgf.cgm.lambdaFieldToName[curField] = fieldName;
+ } else if (capture.capturesThis()) {
+ cgf.cgm.lambdaFieldToName[curField] = "this";
+ } else {
+ cgf.cgm.errorNYI(e->getSourceRange(), "Unhandled capture kind");
+ cgf.cgm.lambdaFieldToName[curField] = "unhandled-capture-kind";
+ }
+
+ // Emit initialization
+ LValue lv =
+ cgf.emitLValueForFieldInitialization(slotLV, curField, fieldName);
+ if (curField->hasCapturedVLAType())
+ cgf.cgm.errorNYI(e->getSourceRange(), "lambda captured VLA type");
+
+ emitInitializationToLValue(captureInit, lv);
+
+ // Push a destructor if necessary.
+ if ([[maybe_unused]] QualType::DestructionKind DtorKind =
+ curField->getType().isDestructedType())
+ cgf.cgm.errorNYI(e->getSourceRange(), "lambda with destructed field");
+ }
+}
+
void AggExprEmitter::VisitCallExpr(const CallExpr *e) {
if (e->getCallReturnType(cgf.getContext())->isReferenceType()) {
cgf.cgm.errorNYI(e->getSourceRange(), "reference return type");
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index e2181b8222aa2..f43a0e60c9f5b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -405,6 +405,7 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
curFn = fn;
const Decl *d = gd.getDecl();
+ curCodeDecl = d;
const auto *fd = dyn_cast_or_null<FunctionDecl>(d);
curFuncDecl = d->getNonClosureContext();
@@ -457,7 +458,36 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
const auto *md = cast<CXXMethodDecl>(d);
if (md->getParent()->isLambda() && md->getOverloadedOperator() == OO_Call) {
- cgm.errorNYI(loc, "lambda call operator");
+ // We're in a lambda.
+ curFn.setLambda(true);
+
+ // Figure out the captures.
+ md->getParent()->getCaptureFields(lambdaCaptureFields,
+ lambdaThisCaptureField);
+ if (lambdaThisCaptureField) {
+ // If the lambda captures the object referred to by '*this' - either by
+ // value or by reference, make sure CXXThisValue points to the correct
+ // object.
+
+ // Get the lvalue for the field (which is a copy of the enclosing object
+ // or contains the address of the enclosing object).
+ LValue thisFieldLValue =
+ emitLValueForLambdaField(lambdaThisCaptureField);
+ if (!lambdaThisCaptureField->getType()->isPointerType()) {
+ // If the enclosing object was captured by value, just use its
+ // address. Sign this pointer.
+ cxxThisValue = thisFieldLValue.getPointer();
+ } else {
+ // Load the lvalue pointed to by the field, since '*this' was captured
+ // by reference.
+ cxxThisValue =
+ emitLoadOfLValue(thisFieldLValue, SourceLocation()).getValue();
+ }
+ }
+ for (auto *fd : md->getParent()->fields()) {
+ if (fd->hasCapturedVLAType())
+ cgm.errorNYI(loc, "lambda captured VLA type");
+ }
} else {
// Not in a lambda; just use 'this' from the method.
// FIXME: Should we generate a new load for each use of 'this'? The fast
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 42f7f401555ca..ecf919396c878 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -73,6 +73,10 @@ class CIRGenFunction : public CIRGenTypeCache {
/// Tracks function scope overall cleanup handling.
EHScopeStack ehStack;
+ llvm::DenseMap<const clang::ValueDecl *, clang::FieldDecl *>
+ lambdaCaptureFields;
+ clang::FieldDecl *lambdaThisCaptureField = nullptr;
+
/// CXXThisDecl - When generating code for a C++ member function,
/// this will hold the implicit 'this' declaration.
ImplicitParamDecl *cxxabiThisDecl = nullptr;
@@ -91,6 +95,8 @@ class CIRGenFunction : public CIRGenTypeCache {
// Holds the Decl for the current outermost non-closure context
const clang::Decl *curFuncDecl = nullptr;
+ /// This is the inner-most code context, which includes blocks.
+ const clang::Decl *curCodeDecl = nullptr;
/// The function for which code is currently being generated.
cir::FuncOp curFn;
@@ -1377,6 +1383,10 @@ class CIRGenFunction : public CIRGenTypeCache {
LValue emitLValueForBitField(LValue base, const FieldDecl *field);
LValue emitLValueForField(LValue base, const clang::FieldDecl *field);
+ LValue emitLValueForLambdaField(const FieldDecl *field);
+ LValue emitLValueForLambdaField(const FieldDecl *field,
+ mlir::Value thisValue);
+
/// Like emitLValueForField, excpet that if the Field is a reference, this
/// will return the address of the reference and not the address of the value
/// stored in the reference.
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 95a7ac0648bb7..073e8d96b773b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -121,6 +121,12 @@ class CIRGenModule : public CIRGenTypeCache {
mlir::Operation *lastGlobalOp = nullptr;
+ /// Keep a map between lambda fields and names, this needs to be per module
+ /// since lambdas might get generated later as part of defered work, and since
+ /// the pointers are supposed to be uniqued, should be fine. Revisit this if
+ /// it ends up taking too much memory.
+ llvm::DenseMap<const clang::FieldDecl *, llvm::StringRef> lambdaFieldToName;
+
/// Tell the consumer that this variable has been instantiated.
void handleCXXStaticMemberVarInstantiation(VarDecl *vd);
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 24aef693024f7..02d6d526c5590 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1548,11 +1548,14 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
llvm::SMLoc loc = parser.getCurrentLocation();
mlir::Builder &builder = parser.getBuilder();
+ mlir::StringAttr lambdaNameAttr = getLambdaAttrName(state.name);
mlir::StringAttr noProtoNameAttr = getNoProtoAttrName(state.name);
mlir::StringAttr visNameAttr = getSymVisibilityAttrName(state.name);
mlir::StringAttr visibilityNameAttr = getGlobalVisibilityAttrName(state.name);
mlir::StringAttr dsoLocalNameAttr = getDsoLocalAttrName(state.name);
+ if (::mlir::succeeded(parser.parseOptionalKeyword(lambdaNameAttr.strref())))
+ state.addAttribute(lambdaNameAttr, parser.getBuilder().getUnitAttr());
if (parser.parseOptionalKeyword(noProtoNameAttr).succeeded())
state.addAttribute(noProtoNameAttr, parser.getBuilder().getUnitAttr());
@@ -1660,6 +1663,9 @@ mlir::Region *cir::FuncOp::getCallableRegion() {
}
void cir::FuncOp::print(OpAsmPrinter &p) {
+ if (getLambda())
+ p << " lambda";
+
if (getNoProto())
p << " no_proto";
diff --git a/clang/test/CIR/CodeGen/lambda.cpp b/clang/test/CIR/CodeGen/lambda.cpp
new file mode 100644
index 0000000000000..033adc60be1ed
--- /dev/null
+++ b/clang/test/CIR/CodeGen/lambda.cpp
@@ -0,0 +1,486 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+// We declare anonymous record types to represent lambdas. Rather than trying to
+// to match the declarations, we establish variables for these when they are used.
+
+void fn() {
+ auto a = [](){};
+ a();
+}
+
+// CIR: cir.func lambda internal private dso_local @_ZZ2fnvENK3$_0clEv(%[[THIS_ARG:.*]]: !...
[truncated]
|
| return LValue(); | ||
| } | ||
|
|
||
| if (e->isNonOdrUse() == NOUR_Constant && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The incubator just asserts e->isNonOdrUse() != NOUR_Constant && "NYI", but we have a couple of test cases that reach this code because of other unimplemented handling that would otherwise intercept the decl sooner. We emit correct code for those cases, but extra qualification is needed to avoid issuing an unnecessary errorNYI diagnostic. These conditions are the conditions for which classic codegen has additional handling here.
bcardosolopes
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This adds support for lambda operators and lambda calls. This does not include support for static lambda invoke, which will be added in a later change.