// // Copyright (c) 2006, Brian Frank and Andy Frank // Licensed under the Academic Free License version 3.0 // // History: // 15 Sep 05 Brian Frank Creation // 5 Sep 06 Brian Frank Ported from Java to Fan // ** ** CallResolver handles the process of resolving a CallExpr or ** UnknownVarExpr to a method call or a field access. ** class CallResolver : CompilerSupport { ////////////////////////////////////////////////////////////////////////// // Construction ////////////////////////////////////////////////////////////////////////// ** ** Construct with NameExpr (base class of CallExpr and UnknownVarExpr) ** new make(Compiler compiler, TypeDef? curType, MethodDef? curMethod, NameExpr expr) : super(compiler) { this.curType = curType this.curMethod = curMethod this.expr = expr this.location = expr.location this.target = expr.target this.name = expr.name call := expr as CallExpr if (call != null) { this.isVar = false this.args = call.args this.found = call.method } else { this.isVar = true this.args = Expr[,] } } ////////////////////////////////////////////////////////////////////////// // Resolve ////////////////////////////////////////////////////////////////////////// ** ** Resolve into a method call or field access ** Expr resolve() { try { if (isStaticLiteral) return result resolveBase find if (result != null) return result insertImplicitThisOrIt resolveToExpr inferClosureType resolveForeign constantFolding castForThisType castForSymbolLiteral ffiCoercion safeToNullable return result } catch (CompilerErr err) { expr.ctype = ns.error return expr } } ////////////////////////////////////////////////////////////////////////// // Static Literal ////////////////////////////////////////////////////////////////////////// ** ** If this is a standalone name without a base target ** such as "Foo" and the name maps to a type name, then ** this is a type literal. ** Bool isStaticLiteral() { if (target == null && isVar) { stypes := curType.unit.importedTypes[name] if (stypes != null && !stypes.isEmpty) { if (stypes.size > 1) throw err("Ambiguous type: " + stypes.join(", "), location) else result = StaticTargetExpr(location, stypes.first) return true } } return false } ////////////////////////////////////////////////////////////////////////// // Resolve Base ////////////////////////////////////////////////////////////////////////// ** ** Resolve the base type which defines the slot we are calling. ** Void resolveBase() { // if target unspecified, then assume a slot on the current // class otherwise the slot must be on the target type if (target == null) { // if we are in a closure - then base is the enclosing class; // if closure is it-block when we need to keep track of it too if (curType.isClosure) { base = curType.closure.enclosingType if (curType.closure.isItBlock) baseIt = curType.closure.itType } else { base = curType } } else { base = target.ctype } // if base is the error type, then we already logged an error // trying to resolve the target and it's pointless to continue if (base === ns.error) throw CompilerErr("ignore", location, null) // sanity check if (base == null) throw err("Internal error", location) } ////////////////////////////////////////////////////////////////////////// // Find ////////////////////////////////////////////////////////////////////////// ** ** Find the method or field with the specified name. ** Void find() { // if already "found", then skip this step if (found != null) return // look it up in base type found = findOn(base) // if we have an it in scope, then also attempt to resolve against it if (baseIt != null) { foundIt := findOn(baseIt) // if we found a match on both base and it, that is an error if (isAmbiguous(found, foundIt)) throw err("Ambiguous slot '$name' on both 'this' ($base) and 'it' ($baseIt)", location) // resolved against implicit it if (foundIt != null) { found = foundIt foundOnIt = true } } // if still not found, then error if (found == null) { if (isVar) { if (target == null) throw err("Unknown variable '$name'", location) else throw err("Unknown slot '$errSig'", location) } else { ct := target as CallExpr if (name == "add" && ct != null && ct.target?.id === ExprId.itExpr && ct.method != null) throw err("'$ct.method.qname' must return This", location) else throw err("Unknown method '$errSig'", location) } } } private CSlot? findOn(CType base) { // if base is the error type, short circuit if (base === ns.error) return null // if simple variable access attempt to lookup as field first, // then as method if that fails (only matters in case of FFI) if (isVar) return base.field(name) ?: base.method(name) // lookup as method CSlot? found := base.method(name) ?: base.field(name) // if we found a FFI field, then try to lookup a method // overloaded by that name; this is a bit hacked b/c since // we don't support overloaded methods in the AST we are // routing this call to the FFI type (such as JavaType); // but this only works if all of our overloads are actually // declared by that class (since we don't support overriding // overloaded methods we can elimate the interface case) if (found is CField && found.isForeign) found = found.parent.method(name) // if we resolve a method call against a field that is an error if (found is CField) throw err("Expected method, not field '$errSig'", location) return found } private Bool isAmbiguous(CSlot? onBase, CSlot? onIt) { // unless we found on both base and baseIt, it is not ambiguous if (onBase == null || onIt == null) return false // if they are both the same static method, it doesn't matter if (onBase.qname == onIt.qname && onBase.isStatic) return false // if we are calling an instance slot in a static context, // then we can assume that we are binding to it if (!onBase.isStatic && !onIt.isStatic && curType.closure.enclosingSlot.isStatic) return false return true } private Str errSig() { return "${base.qname}.${name}" } ////////////////////////////////////////////////////////////////////////// // Implicit This ////////////////////////////////////////////////////////////////////////// ** ** If the call has no explicit target, and is a instance field ** or method, then we need to insert an implicit this or it. ** private Void insertImplicitThisOrIt() { if (target != null) return if (found.isStatic || found.isCtor) return if (curMethod.isStatic) return if (foundOnIt) { target = ItExpr(location, baseIt) } else if (curType.isClosure) { closure := curType.closure if (!closure.enclosingSlot.isStatic) target = FieldExpr(location, ThisExpr(location, closure.enclosingType), closure.outerThisField) } else { target = ThisExpr(location, curType) } } ////////////////////////////////////////////////////////////////////////// // Resolve Expr Type ////////////////////////////////////////////////////////////////////////// ** ** Compute the expression type the call itself (what gets left on the stack). ** private Void resolveToExpr() { if (found is CField) { result = resolveToFieldExpr } else { result = resolveToCallExpr } } private CallExpr resolveToCallExpr() { method := (CMethod)found call := expr as CallExpr if (call == null) { call = CallExpr(location) call.name = name call.args = args } call.target = target call.isSafe = expr.isSafe call.noParens = isVar call.method = method if (method.isCtor) call.ctype = method.parent else call.ctype = method.returnType return call } private FieldExpr resolveToFieldExpr() { f := (CField)found field := FieldExpr(location) field.target = target field.name = name field.field = f field.ctype = f.fieldType field.isSafe = expr.isSafe return field } ////////////////////////////////////////////////////////////////////////// // Infer Closure Type ////////////////////////////////////////////////////////////////////////// ** ** If the last argument to the resolved call is a closure, ** then use the method to infer the function type ** private Void inferClosureType() { if (result is CallExpr) { base := foundOnIt ? this.baseIt : this.base result = inferClosureTypeFromCall(this, result, base) } } ** ** If the last argument to the resolved call is a closure, ** then use the method to infer the function type. If the ** last arg is a closure, but the call doesn't take a closure, ** then translate into an implicit call to Obj.with ** static Expr inferClosureTypeFromCall(CompilerSupport support, CallExpr call, CType base) { // check if last argument is closure c := call.args.last as ClosureExpr if (c == null) return call // if the resolved slot is a method where the last param // is expected to be a function type, then use that to // infer the type signature of the closure m := call.method lastParam := m.params.last?.paramType?.deref?.toNonNullable as FuncType if (lastParam != null && call.args.size == m.params.size && c.signature.params.size <= lastParam.params.size) { if (call.method.name == "with") lastParam = FuncType.makeItBlock(base) else lastParam = lastParam.parameterizeThis(base) c.setInferredSignature(lastParam) return call } // otherwise if the closure is an it-block, we infer // its type to be the result of the target expression if (c.isItBlock) { // if call is This, switch it to base (passes thru to toWith) if (call.ctype.isThis) call.ctype = base // can't chain it-block if call returns Void if (call.ctype.isVoid) { support.err("Cannot apply it-block to Void expr", call.location) return call } // remove the function parameter and turn this into: // call(args).toWith(c) call.args.removeAt(-1) return c.toWith(call) } return call } ////////////////////////////////////////////////////////////////////////// // FFI ////////////////////////////////////////////////////////////////////////// ** ** If we have a FFI call, then give the foreign bridge a chance ** to resolve the method and deal with method overloading. Note ** at this point we've already resolved the call by name to *some* ** method (in the find step). But this callback gives the bridge ** a chance to resolve to the *correct* overloaded method. We need ** to this during ResolveExpr in order to infer local variables ** correctly. ** private Void resolveForeign() { bridge := found.usesBridge if (bridge != null && result is CallExpr) result = bridge.resolveCall(result) } ////////////////////////////////////////////////////////////////////////// // Constant Folding ////////////////////////////////////////////////////////////////////////// ** ** If the epxression is a call, check for constant folding. ** private Void constantFolding() { // only do const folding on method calls (which inculdes shortcut ops) call := result as CallExpr if (call == null) return // skip constant folding for testSys unless this is symbols if (curType != null && compiler.pod.name == "testSys") return result = ConstantFolder(compiler).fold(call) } ////////////////////////////////////////////////////////////////////////// // Cast for This Type ////////////////////////////////////////////////////////////////////////// ** ** If the epxression is a call which returns sys::This, ** then we need to insert an implicit cast. ** private Void castForThisType() { // only care about calls that return This if (!result.ctype.isThis) return // check that we are calling a method method := found as CMethod if (method == null) return // the result of a method which returns This // is always the base target type - if we aren't // calling against the original declaring type // then we also need an implicit cast operation base := foundOnIt ? this.baseIt : this.base result.ctype = base if (method.inheritedReturnType != base) result = TypeCheckExpr.coerce(result, base) { from = method.inheritedReturnType } } ////////////////////////////////////////////////////////////////////////// // Cast for Symbol Literal ////////////////////////////////////////////////////////////////////////// ** ** If the epxression base expression is a symbol literal ** and we are calling one of the value methods then we ** know the value type at compile time. ** private Void castForSymbolLiteral() { // only care about methods on symbol literals if (target == null || target.id !== ExprId.symbolLiteral) return symbol := ((SymbolExpr)target).symbol if (symbol == null) return // only care about method val/defVal calls on the symbol literal method := found as CMethod if (method == null) return if (method.qname != "sys::Symbol.val" && method.qname != "sys::Symbol.defVal") return // the result of a method which returns This // is always the base target type - if we aren't // calling against the original declaring type // then we also need an implicit cast operation from := result.ctype result = TypeCheckExpr.coerce(result, symbol.of) { it.from = from } } ////////////////////////////////////////////////////////////////////////// // FFI Coercion ////////////////////////////////////////////////////////////////////////// ** ** If this field access or method call returns a type which ** isn't directly represented in the Fan type system, then ** implicitly coerce it ** private Void ffiCoercion() { if (result.ctype.isForeign) { foreign := result.ctype inferred := foreign.inferredAs if (foreign !== inferred) { result = foreign.bridge.coerce(result, inferred) |,| { throw err("Cannot coerce call return to Fan type", location) } } } } ////////////////////////////////////////////////////////////////////////// // Safe to Nullable ////////////////////////////////////////////////////////////////////////// ** ** If the epxression is a safe call using "?.", then ** the resulting expression type is nullable. ** private Void safeToNullable() { if (expr.isSafe) result.ctype = result.ctype.toNullable } ////////////////////////////////////////////////////////////////////////// // Fields ////////////////////////////////////////////////////////////////////////// TypeDef? curType // current type of scope MethodDef? curMethod // current method of scope NameExpr expr // original expression being resolved Location location // location of original expression Expr? target // target base or null Str name // slot name to resolve Bool isVar // are we resolving simple variable Expr[] args // arguments or null if simple variable CType? base // resolveBase() CType? baseIt // resolveBase() CSlot? found // find() Bool foundOnIt // was find() resolved against it Expr? result // resolveToExpr() }