The correct way to build an implicit callExpr from a optional base

Let us have

let array: [Int]? = [0]

I need to build array?.makeIterator() out of array for later use. Normally it would be array.makeIterator(), but I need optional chaining because I expect an optional sequence. How to build it if array were not optional is described in TypeChecker::callWitness with a couple of fixits.

This is the basic workflow I have based on what's in callWitness.

auto BOE = new (Context) BindOptionalExpr(base, base->getEndLoc(), 0);

auto UDE = new (Context) UnresolvedDotExpr(
  BOE, SourceLoc(), witness.getDecl()->getFullName(),
  DeclNameLoc(BOE->getEndLoc()), /*Implicit=*/true);

unresolvedDot->setFunctionRefKind(FunctionRefKind::SingleApply);
auto dotLocator = cs.getConstraintLocator(unresolvedDot);

Type openedFullType, openedType;
std::tie(openedFullType, openedType)
  = cs.getTypeOfMemberReference(base->getType(), witness.getDecl(), dc, 
                                /*isDynamicResult=*/false,
                                FunctionRefKind::DoubleApply,
                                dotLocator);

auto getType = [&](const Expr *E) -> Type {
    return cs.getType(E);
};

auto call = CallExpr::create(Context, UDE, base->getStartLoc(), arguments,
                             argLabels, {}, base->getEndLoc(),
                             /*trailingClosure=*/nullptr,
                             /*implicit=*/true, getType);

auto OEE = new (Context) OptionalEvaluationExpr(call);
cs.cacheSubExprTypes(OEE);

auto openedFuncType =  openedType->castTo<FunctionType>();

cs.addConstraint(
  ConstraintKind::ArgumentTupleConversion, cs.getType(call->getArg()),
  FunctionType::composeInput(cs.getASTContext(),
                             openedFuncType->getParams(), false),
                             cs.getConstraintLocator(call, ConstraintLocator::ApplyArgument));

// solve the CS.

The constraint system before solving it:

Type Variables:
  $T0 as [Int]?
Active Constraints:
  $T0 conforms to Collection
  $T0.Iterator equal IndexingIterator<$T0>

The main problem is this $T0 conforms to Collection constraint that is added by getTypeOfMemberReference and makes everything break.
If I pass base->getType()->getOptionalObjectType() to getTypeOfMemberReference instead, it's a bit better since T0 becomes [Int], but still crashes on an unhandled coercion further on.

P.S. Judging from this comment, this approach is more of a hack, and a bad one – I would be glad to learn the proper way of doing whatever the comment refers to.

@rudkx @Slava_Pestov @Douglas_Gregor

Take a look at OptionalEvaluationExpr and BindOptionalExpr. You want to use the BindOptionalExpr as the 'base" for your member lookup.

1 Like

Ah, so member lookup with an optional base considers the object type's members as well. Thank you!

No, that is not correct. Optional evaluation, like foo?.bar(), is modeled as an OptionalEvaluationExpr that takes 'foo', of Optional type. The member access itself is performed on the BindOptionalExpr, which has non-optional type -- it represents the unwrapped value.

I am already modeling it this way in the code snippet up there. Do you mean to enclose my array declaration reference expression with optional type in a BindOptionalExpr before calling callWitness, then pass that as the base so that everything happens "as if array isn't optional" and finally enclose it in an OptionalEvaluationExpr after returning? I'm not sure where to perform type-checking for that part though.

The base expression you pass to cs.getTypeOfMemberReference() should be the BindOptionalExpr, not 'base', I think.

I'm not sure where to perform type-checking for that part though.

I'm assuming you're working on something in CSApply.cpp. Please try to build fully type-checked AST here, instead of calling typeCheckExpression() again. Also note that callWitness() is likely to be radically simplified or removed altogether at some point.

The problem is that cs.getTypeOfMemberReference() takes a type, not an expression, but I've got a few ideas. Of course, I'll make sure everything is being type-checked. Thank you.

Right, you pass in the type of the BindOptionalExpr, which is the unwrapped non-optional type you're actually doing the lookup into.