Application Resolution
This chapter describes how Clef resolves application expressions, including function and method applications. This is a component of the overall Inference Procedures.
Resolving Application Expressions
Application expressions that use dot notation - such as x.Y<int>.Z(g).H.I.j - are resolved
according to a set of rules that take into account the many possible shapes and forms of these
expressions and the ambiguities that may occur during their resolution. This section specifies the
exact algorithmic process that is used to resolve these expressions.
Resolution of application expressions proceeds as follows:
Repeatedly decompose the application expression into a leading expression
exprand a list of projectionsprojs. Each projection has the following form:.long-ident-or-opis a dot lookup projection.expris an application projection.<types>is a type application projection.
For example:
x.y.Z(g).H.I.jdecomposes intox.y.Zand projections(g),.H.I.j.x.M<int>(g)decomposes intox.Mand projections<int>,(g).f xdecomposes intofand projectionx.
Note: In this specification we write sequences of projections by juxtaposition; for example,
(expr).long-ident<types>(expr). We also write (.rest+projs) to refer to adding a residue long identifier to the front of a list of projections, which results inprojsifrestis empty and.rest projsotherwise.After decomposition:
- If
expris a long identifier expressionlong-ident, apply Unqualified Lookup (§ Unqualified Lookup) onlong-identwith projectionsprojs. - If
expris not such an expression, check the expression against an arbitrary initial typety, to generate an elaborated expressionexpr. Then processexpr,ty, andprojsby using Expression-Qualified Lookup (§ Expression Qualified Lookup)
- If
Unqualified Lookup
Given an input long-ident and projections projs, Unqualified Lookup computes the result of
“looking up” long-ident.projs in an environment env. The first part of this process resolves a prefix
of the information in long-ident.projs, and recursive resolutions typically use Expression-Qualified
Resolution to resolve the remainder.
For example, Unqualified Lookup is used to resolve the vast majority of identifier references in F#
code, from simple identifiers such as sin, to complex accesses such as
Array.init 10 id |> Array.length.
Unqualified Lookup proceeds through the following steps:
- Resolve
long-identby using Name Resolution in Expressions (§ Name Resolution). This returns a name resolution itemitemand a residue long identifierrest.
For example, the result of Name Resolution in Expressions for v.X.Y may be a value reference v
along with a residue long identifier X.Y. Likewise, N.X(args).Y may resolve to an overloaded
method N.X and a residue long identifier Y.
Name Resolution in Expressions also takes as input the presence and count of subsequent type
arguments in the first projection. If the first projection in projs is <tyargs>, Unqualified Lookup
invokes Name Resolution in Expressions with a known number of type arguments. Otherwise, it
is invoked with an unknown number of type arguments.
- Apply Item-Qualified Lookup for
itemand (rest+projs).
Item-Qualified Lookup
Given an input item item and projections projs, Item-Qualified Lookup computes the projection
item.projs. This computation is often a recursive process: the first resolution uses a prefix of the
information in item.projs, and recursive resolutions resolve any remaining projections.
Item-Qualified Lookup proceeds as follows:
- If
itemis not one of the following, return an error:- A named value
- A unique type
- A union case
- A group of named types
- A group of methods
- A group of indexer getter properties
- A single non-indexer getter property
- A static F# field
- A static field
- An implicitly resolved symbolic operator name
- If the first projection is
<types>, then we say the resolution has a type application<types>with remaining projections. - Otherwise, checking proceeds as follows.
If
itemis a value referencevthen first Instantiate the type scheme ofv, which results in a typety. Apply these rules:- If the first projection is
<types>, process the types and use the results as the arguments to instantiate the type scheme. - If the first projection is not
<types>, the type scheme is freshly instantiated. - If the value has the
RequiresExplicitTypeArgumentsattribute, the first projection must be<types>. - If the value has type
byref<ty2>, add a byref dereference to the elaborated expression. - Insert implicit flexibility for the use of the value (§ Implicit Insertion of Flexibility for Uses of Functions and Members).
Then Apply Expression-Qualified Lookup for type
tyand any remaining projections.- If the first projection is
If
itemis a type name, whereprojsbegins with<types>.long-ident- Process the types and use the results as the arguments to instantiate the named
type reference, thus generating a type
ty. - Apply Name Resolution for Members to
tyandlong-ident, which generates a newitem. - Apply Item-Qualified Lookup to the new
itemand any remaining projections.
- Process the types and use the results as the arguments to instantiate the named
type reference, thus generating a type
If
itemis a group of type names whereprojsbegins with<types>orexprorprojsis empty- Process the types and use the results as the arguments to instantiate the named
type reference, thus generating a type
ty. - Process the object construction
ty(expr)as an object constructor call in the same way asnew ty(expr). Ifprojsis empty then process the object constructiontyas an object constructor call in the same way as(fun arg -> new ty(arg)), i.e. resolve the object constructor call with no arguments. - Apply Expression-Qualified Lookup to
itemand any remaining projections.
- Process the types and use the results as the arguments to instantiate the named
type reference, thus generating a type
If
itemis a group of method references- Apply Method Application Resolution for the method group. Method Application Resolution
accepts an optional set of type arguments and a syntactic expression
argument. Determine the arguments based on what
projsbegins with:<types> expr, then use<types>as the type arguments andexpras the expression argument.expr, then useexpras the expression argument.- anything else, use no expression argument or type arguments.
- If the result of Method Application Resolution is labeled with the
RequiresExplicitTypeArgumentsattribute, then explicit type arguments are required. - Let
ftybe the actual return type that results from Method Application Resolution. Apply Expression-Qualified Lookup toftyand any remaining projections.
- Apply Method Application Resolution for the method group. Method Application Resolution
accepts an optional set of type arguments and a syntactic expression
argument. Determine the arguments based on what
If
itemis a group of property indexer references- Apply Method Application Resolution, and use the underlying getter indexer methods for the method group.
- Determine the arguments to Method Application Resolution as described for a group of methods.
If
itemis a static field reference- Check the field for accessibility and attributes.
- Let
ftybe the actual type of the field, taking into account the typetyvia which the field was accessed in the case where this is a field in a generic type. - Apply Expression-Qualified Lookup to
ftyandprojs.
If
itemis a union case tag, exception tag, or active pattern result element tag- Check the tag for accessibility and attributes.
- If
projsbegins withexpr, useexpras the expression argument. - Otherwise, use no expression argument or type arguments. In this case, build a function expression for the union case.
- Let
ftybe the actual type of the union case. - Apply Expression-Qualified Lookup to
ftyand remainingprojs.
If
itemis a unique type nameCwhereprojsis empty- Check the type for accessibility and attributes.
- Process the types using a new instantiation for
C, thus generating a typety, and process the object constructionfun v -> new ty(v)as an object constructor call.
If
itemis an event reference- Check the event for accessibility and attributes.
- Let
ftybe the actual type of the event. - Apply Expression-Qualified Lookup to
ftyandprojs.
If
itemis an implicitly resolved symbolic operator nameopIf
opis a unary, binary or the ternary operator ?<-, resolve to the following expressions, respectively:(fun (x:^a) -> (^a : static member (op) : ^a -> ^b) x) (fun (x:^a) (y:^b) -> ((^a or ^b) : static member (op) : ^a * ^b -> ^c) (x,y)) (fun (x:^a) (y:^b) (z:^c) -> ((^a or ^b or ^c) : static member (op) : ^a * ^b * ^c -> ^d) (x,y,z))The resulting expressions are static member constraint invocation expressions (§ Member Constraint Invocation Expressions), which enable the default interpretation of operators by using type-directed member resolution.
Recheck the entire expression with additional subsequent projections
.projs.
Expression-Qualified Lookup
Given an elaborated expression expr of type ty, and projections projs, Expression-Qualified Lookup
computes the “lookups or applications” for expr.projs.
Expression-Qualified Lookup proceeds through the following steps:
Inspect
projsand process according to the following table.projsAction Comments Empty Assert that the type of the overall, original application expression is ty.Checking is complete. Starts with (expr2)Apply Function Application Resolution (§ Function Application Resolution). Checking is complete when Function Application Resolution returns. Starts with <types>Fail. Type instantiations may not be applied to arbitrary expressions; they can apply only to generic types, generic methods, and generic values. Starts with .long-identResolve long-identusing Name Resolution for Members (§ Name Resolution in Expressions)_. Return a name resolution itemitemand a residue long identifierrest. Continue processing at step 2.For example, for ty = stringandlong-ident = Length, Name Resolution for Members returns a property reference to theLengthproperty of the string type.If Step 1 returned an
itemandrest, report an error ifitemis not one of the following:- A group of methods.
- A group of instance getter property indexers.
- A single instance, non-indexer getter property.
- A single instance F# field.
- A single instance field.
Proceed based on
itemas follows:If
itemis a group of methodsApply Method Application Resolution for the method group. Method Application Resolution accepts an optional set of type arguments and a syntactic expression argument. If
projsbegins with:<types>(arg), then use<types>as the type arguments andargas the expression argument.(arg), then useargas the expression argument.- otherwise, use no expression argument or type arguments.
Let
ftybe the actual return type resulting from Method Application Resolution. Apply Expression-Qualified Lookup toftyand any remaining projections.
If
itemis a group of indexer properties- Apply Method Application Resolution and use the underlying getter indexer methods for the method group.
- Determine the arguments to Method Application Resolution as described for a group of methods.
If
itemis a non-indexer getter property- Apply Method Application Resolution for the method group that contains
only the getter method for the property, with no type arguments and one
()argument.
- Apply Method Application Resolution for the method group that contains
only the getter method for the property, with no type arguments and one
If
itemis an instance intermediate language (IL) or F# fieldF- Check the field for accessibility and attributes.
- Let
ftybe the actual type of the field (taking into account the typetyby which the field was accessed). - Assert that
tyis a subtype of the actual containing type of the field. - Produce an elaborated form for
expr.F. IfFis a field in a value type then take the address ofexprby using the AddressOf(expr, NeverMutates) operation § Taking the Address of an Elaborated Expression. - Apply Expression-Qualified Lookup to
ftyandprojs.
Function Application Resolution
Given expressions f and expr where f has type ty, and given subsequent projections projs, Function
Application Resolution does the following:
Asserts that
fhas typety1 -> ty2for new inference variablesty1andty2.If the assertion succeeds:
- Check
exprwith the initial typety1. - Process
projsusing Expression-Qualified Lookup againstty2.
- Check
If the assertion fails, and
exprhas the form{ computation-expr }:- Check the expression as the computation expression form
f { computation-expr }, giving result typety1. - Process
projsusing Expression-Qualified Lookup againstty1.
- Check the expression as the computation expression form
Method Application Resolution
Given a method group M, optional type arguments <ActualTypeArgs>, an optional syntactic argument
obj, an optional syntactic argument arg, and overall initial type ty, Method Application Resolution
resolves the overloading based on the partial type information that is available. It also:
- Resolves optional and named arguments.
- Resolves “out” arguments.
- Resolves post-hoc property assignments.
- Applies method application resolution.
- Inserts ad hoc conversions that are only applied for method calls.
If no syntactic argument is supplied, Method Application Resolution tries to resolve the use of the method as a first class value, such as the method call in the following example:
List.map String.length ["hello"; "world"]Method Application Resolution proceeds through the following steps:
Restrict the candidate method group
Mto those methods that are accessible from the point of resolution.If an argument
argis present, determine the sets of unnamed and named actual arguments,UnnamedActualArgsandNamedActualArgs:Decompose
arginto a list of arguments:- If
argis a syntactic tuplearg1, ..., argN, use these arguments. - If
argis a syntactic unit value(), use a zero-length list of arguments.
- If
For each argument:
- If
argis a binary expression of the formname=expr, it is a named actual argument. - Otherwise,
argis an unnamed actual argument.
- If
If there are no named actual arguments, and
Mhas only one candidate method, which accepts only one required argument, ignore the decomposition ofargto tuple form. Instead,argitself is the only named actual argument.All named arguments must appear after all unnamed arguments.
Examples:
x.M(1, 2)has two unnamed actual arguments.x.M(1, y = 2)has one unnamed actual argument and one named actual argument.x.M(1, (y = 2))has two unnamed actual arguments.x.M( printfn "hello"; ())has one unnamed actual argument.x.M((a, b))has one unnamed actual argument.x.M(())has one unnamed actual argument.
Determine the named and unnamed prospective actual argument types, called
ActualArgTypes.If an argument
argis present, the prospective actual argument types are fresh type inference variables for each unnamed and named actual argument.- If the argument has the syntactic form of an address-of expression
&exprafter ignoring parentheses around the argument, equate this type with a typebyref<ty>for a fresh typety. - If the argument has the syntactic form of a function expression
fun pat1 ... patn -> exprafter ignoring parentheses around the argument, equate this type with a typety1 -> ... tyn -> rtyfor fresh typesty1 ... tyn.
- If the argument has the syntactic form of an address-of expression
If no argument
argis present:If the method group contains a single method, the prospective unnamed argument types are one fresh type inference variable for each required, non-“out” parameter that the method accepts.
If the method group contains more than one method, the expected overall type of the expression is asserted to be a function type
dty -> rty.If
dtyis a tuple type(dty1 * ... * dtyN), the prospective argument types are(dty1, ..., dtyN).If
dtyisunit, then the prospective argument types are an empty list.If
dtyis any other type, the prospective argument types aredtyalone.
Subsequently:
The method application is considered to have one unnamed actual argument for each prospective unnamed actual argument type.
The method application is considered to have no named actual arguments.
For each candidate method in
M, attempt to produce zero, one, or two prospective method callsM~possibleas follows:If the candidate method is generic and has been generalized, generate fresh type inference variables for its generic parameters. This results in the
FormalTypeArgsforM~possible.Determine the named and unnamed formal parameters , called
NamedFormalArgsandUnnamedFormalArgsrespectively, by splitting the formal parameters forMinto parameters that have a matching argument inNamedActualArgsand parameters that do not.If the number of
UnnamedFormalArgsexceeds the number ofUnnamedActualArgs, then modifyUnnamedFormalArgsas follows:- Determine the suffix of
UnnamedFormalArgsbeyond the number ofUnnamedActualArgs. - If all formal parameters in the suffix are
outarguments withbyreftype, remove the suffix fromUnnamedFormalArgsand call itImplicitlyReturnedFormalArgs. - If all formal parameters in the suffix are optional arguments, remove the suffix from
UnnamedFormalArgsand call itImplicitlySuppliedFormalArgs.
- Determine the suffix of
If the last element of
UnnamedFormalArgshas theParamArrayattribute and typepty[]for somepty, then modifyUnnamedActualArgsas follows:If the number of
UnnamedActualArgsexceeds the number ofUnnamedFormalArgs - 1, produce a prospective method call namedParamArrayActualArgsthat has the excess ofUnnamedActualArgsremoved.If the number of
UnnamedActualArgsequals the number ofUnnamedFormalArgs - 1, produce two prospective method calls:- One has an empty
ParamArrayActualArgs. - One has no
ParamArrayActualArgs.
- One has an empty
If
ParamArrayActualArgshas been produced, thenM~possibleis said to use ParamArray conversion with typepty.
Associate each
name = arginNamedActualArgswith a target. A target is a named formal parameter, a settable return property, or a settable return field as follows:- If one of the arguments in
NamedFormalArgshas namename, that argument is the target. - If the return type of
M, before the application of any type argumentsActualTypeArgs, contains a settable propertyname, thennameis the target. The available properties include any property extension members of type, found by consulting the ExtensionsInScope table. - If the return type of
M, before the application of any type argumentsActualTypeArgs, contains a settable fieldname, thennameis the target.
- If one of the arguments in
No prospective method call is generated if any of the following are true:
- A named argument cannot be associated with a target.
- The number of
UnnamedActualArgsis less than the number ofUnnamedFormalArgsafter steps 4 a-e. - The number of
ActualTypeArgs, if any actual type arguments are present, does not precisely equal the number ofFormalTypeArgsforM. - The candidate method is static and the optional syntactic argument
objis present, or the candidate method is an instance method andobjis not present.
Attempt to apply initial types before argument checking. If only one prospective method call
M~possibleexists, assertM~possibleby performing the following steps:Verify that each
ActualTypeArgiis equal to its correspondingFormalTypeArgi.Verify that the type of
objis a subtype of the containing type of the methodM.For each
UnnamedActualArgiandUnnamedFormalArgi, verify that the correspondingActualArgTypecoerces to the type of the corresponding argument ofM.If
M~possibleuses ParamArray conversion with typepty, then for eachParamArrayActualArgi, verify that the correspondingActualArgTypecoerces topty.For each
NamedActualArgithat has an associated formal parameter target, verify that the correspondingActualArgTypecoerces to the type of the corresponding argument ofM.For each
NamedActualArgithat has an associated property or field setter target, verify that the correspondingActualArgTypecoerces to the type of the property or field.Verify that the prospective formal return type coerces to the expected actual return type. If the method
Mhas return typerty, the formal return type is defined as follows:- If the prospective method call contains
ImplicitlyReturnedFormalArgswith typety1, ..., tyN, the formal return type isrty * ty1 * ... * tyN. Ifrtyisunitthen the formal return type isty1 * ... * tyN. - Otherwise the formal return type is
rty.
- If the prospective method call contains
Check and elaborate argument expressions. If
argis present:- Check and elaborate each unnamed actual argument expression
argi. Use the corresponding type inActualArgTypesas the initial type. - Check and elaborate each named actual argument expression
argi. Use the corresponding type inActualArgTypesas the initial type.
- Check and elaborate each unnamed actual argument expression
Choose a unique
M~possibleaccording to the following rules:- For each
M~possible, determine whether the method is applicable by attempting to assertM~possibleas described in step 4a). If the actions in step 4a detect an inconsistent constraint set (§ Constraint Solving), the method is not applicable. Regardless, the overall constraint set is left unchanged as a result of determining the applicability of eachM~possible. - If a unique applicable
M~possibleexists, choose that method. Otherwise, choose the unique bestM~possibleby applying the following criteria, in order:- Prefer candidates whose use does not constrain the use of a user-introduced generic type annotation to be equal to another type.
- Prefer candidates that do not use ParamArray conversion. If two candidates both use
ParamArray conversion with types
pty1andpty2, andpty1feasibly subsumespty2, prefer the second; that is, use the candidate that has the more precise type. - Prefer candidates that do not have
ImplicitlyReturnedFormalArgs. - Prefer candidates that do not have
ImplicitlySuppliedFormalArgs. - If two candidates have unnamed actual argument types
ty11 ... ty1nandty21 ... ty2n, and eachty1ifeasibly subsumesty2i, then prefer the second candidate. That is, prefer any candidate that has the more specific actual argument types. - Prefer candidates that are not extension members over candidates that are.
- To choose between two extension members, prefer the one that results from the most recent use of open.
- Prefer candidates that are not generic over candidates that are generic - that is, prefer
candidates that have empty
ActualArgTypes.
Report an error if steps 1) through 8) do not result in the selection of a unique better method.
- For each
Once a unique best
M~possibleis chosen, commit that method.Apply attribute checks.
Build the resulting elaborated expression by following these steps:
If the type of
objis a variable type or a value type, take the address ofobjby using the AddressOf(obj , PossiblyMutates)operation (§ Taking the Address of an Elaborated Expression).Build the argument list by:
- Passing each argument corresponding to an
UnamedFormalArgswhere the argument is an optional argument as aSomevalue. - Passing a
Nonevalue for each argument that corresponds to anImplicitlySuppliedFormalArgs. - Applying coercion to arguments.
- Passing each argument corresponding to an
Bind
ImplicitlyReturnedFormalArgsarguments by introducing mutable temporaries for each argument, passing them asbyrefparameters, and building a tuple from these mutable temporaries and any method return value as the overall result.For each
NamedActualArgswhose target is a settable property or field, assign the value into the property.If
argis not present, return a function expression that represents a first class function value.
Two additional rules apply when checking arguments (see § Type Directed Conversions at Member Invocations for examples):
If a formal parameter has delegate type
D, an actual argumentfarghas known typety1 -> ... -> tyn -> rty, and the number of arguments of the Invoke method of delegate typeDis preciselyn, interpret the formal parameter in the same way as the following:new D (fun arg1 ... argn -> farg arg1 ... argn).For more information on the conversions that are automatically applied to arguments, see § Optional Arguments to Method Members.
If a formal parameter is an
outparameter of typebyref<ty>, and an actual argument type is not a byref type, interpret the actual parameter in the same way as typeref<ty>. That is, an F# reference cell can be passed where abyref<ty>is expected.
One effect of these additional rules is that a method that is used as a first class function value can resolve even if a method is overloaded and no further information is available. For example:
let builder = StringBuilder()
let append = builder.Append;;Method Application Resolution results in the following, despite the fact that StringBuilder.Append may be overloaded:
val append : string -> StringBuilderThe reason is that if the initial type contains no information about the expected number of arguments, the F# compiler assumes that the method has one argument.
Additional Propagation of Known Type Information in F# 3.1
In the above descreiption of F# overload resolution, the argument expressions of a call to an overloaded set of methods
callerObjArgTy.Method(callerArgExpr1 , ... callerArgExprN)calling
calledObjArgTy.Method(calledArgTy1, ... calledArgTyN)In F# 3.1 and subsequently, immediately prior to checking argument expressions, each argument position of the unnamed caller arguments for the method call is analysed to propagate type information extracted from method overloads to the expected types of lambda expressions. The new rule is applied when
the candidates are overloaded
the caller argument at the given unnamed argument position is a syntactic lambda, possible parenthesized
all the corresponding formal called arguments have
calledArgTyeither of- function type
calledArgDomainTy1 -> ... -> calledArgDomainTyN -> calledArgRangeTy(after taking into account “function to delegate” adjustments), or - some other type which would cause an overload to be discarded
- function type
at least one overload has enough curried lambda arguments for it corresponding expected function type
In this case, for each unnamed argument position, then for each overload:
- Attempt to solve
callerObjArgTy = calledObjArgTyfor the overload, if the overload is for an instance member. When making this application, only solve type inference variables present in thecalledObjArgTy. If any of these conversions fail, then skip the overload for the purposes of this rule - Attempt to solve
callerArgTy = (calledArgDomainTy1_ -> ... -> calledArgDomainTyN_ -> ?). If this fails, then skip the overload for the purposes of this rule
Conditional Compilation of Member Calls
If a member definition has the Conditional attribute, then any application of
the member is adjusted as follows:
- The
Conditional("symbol")attribute may apply to methods only. - Methods that have the
Conditionalattribute must have return typeunit. The return type may be checked either on use of the method or definition of the method. - If
symbolis not in the current set of conditional compilation symbols, the compiler eliminates application expressions that resolve to calls to members that have theConditionalattribute and ensures that arguments are not evaluated. Elimination of such expressions proceeds first with static members and then with instance members, as follows:- Static members:
Type.M(args)=>() - Instance members:
expr.M(args)=>()
- Static members:
Implicit Insertion of Flexibility for Uses of Functions and Members
At each use of a data constructor, named function, or member that forms an expression, flexibility is implicitly added to the expression. This flexibility is associated with the use of the function or member, according to the inferred type of the expression. The added flexibility allows the item to accept arguments that are statically known to be subtypes of argument types to a function without requiring explicit upcasts
The flexibility is added by adjusting each expression expr which represents a use of a function or
member as follows:
The type of the function or member is decomposed to the following form:
ty11 * ... * ty1n -> ... -> tym1 * ... * tymn -> rtyIf the type does not decompose to this form, no flexibility is added.
The positions
tyijare called the “parameter positions” for the type. For each parameter position wheretyijis not a sealed type, and is not a variable type, the type is replaced by a fresh type variablety'ijwith a coercion constraintty'ij :> tyij.After the addition of flexibility, the expression elaborates to an expression of type
ty'11 * ... * ty'1n -> ... -> ty'm1 * ... * ty'mn -> rtybut otherwise is semantically equivalent to
exprby creating an anonymous function expression and inserting appropariate coercions on arguments where necessary.
This means that F# functions whose inferred type includes an unsealed type in argument position may be passed subtypes when called, without the need for explicit upcasts. For example:
type Base() =
member b.X = 1
type Derived(i : int) =
inherit Base()
member d.Y = i
let d = new Derived(7)
let f (b : Base) = b.X
// Call f: Base -> int with an instance of type Derived
let res = f d
// Use f as a first-class function value of type : Derived -> int
let res2 = (f : Derived -> int)The F# compiler determines whether to insert flexibility after explicit instantiation, but before any arguments are checked. For example, given the following:
let M<'b>(c :'b, d :'b) = 1
let obj = new obj()
let str = ""these expressions pass type-checking:
M<obj>(obj, str)
M<obj>(str, obj)
M<obj>(obj, obj)
M<obj>(str, str)
M(obj, obj)
M(str, str)These expressions do not, because the target type is a variable type:
M(obj, str)
M(str, obj)