Lambda Specification, Part D: Poly Expressions

Navigation: Overview - Part A - Part B - Part C - Part D - Part E - Part F - Part G - Part H - Part J
Sections: 5 - 5.2 - 5.3 - 5.4 - 5.5 - 5.5.1 - 5.5.2 - 5.6 - 15.1 - 15.2 - 15.3 - 15.8 - 15.8.5 - 15.9 - 15.12 - 15.16 - 15.25 - 15.25.1 - 15.25.2 - 15.25.3 - 15.29
Version 0.6.2. Copyright © 2012 Oracle America, Inc. Legal Notice.

Summary

Every expression written in the Java programming language either produces no result (void) or has a type that can be deduced at compile time. When an expression appears in most contexts, it must be compatible with a type expected in that context; this type is called the target type. For convenience, compatibility of an expression with its surrounding context is facilitated by the language in two ways:

If neither strategy is able to produce the appropriate type, an error occurs at compile time.

Given certain meanings of names, the type of a standalone expression (an expression that is not a poly expression) can be determined entirely from the contents of the expression. In contrast, the type of a poly expression may be influenced by the expression's target type.

The following forms of expressions may be poly expressions:

To determine whether an expression of one of these forms is a poly expression, we may consider the kind of context in which the expression appears and the content of the expression.

Generic method invocation expressions, along with class instance creation expressions that use a diamond <>, are poly expressions when they appear in assignment or invocation contexts. This allows type argument inference to depend on context.

Lambda expressions and method references are always poly expressions; their typing rules are covered by Part E.

Conditional operator expressions may be poly expressions if they appear in assignment or invocation contexts, unless both operands produce primitives (or boxed primitives). When they are poly expressions, the target type is "pushed down" to each operand.

Similarly, parenthesized expressions that wrap poly subexpressions are poly expressions, passing the target type on to the subexpression.

Casts can be used to explicitly "tag" a lambda expression or a method reference with a particular target type. To provide an appropriate degree of flexibility, the target type may be a list of types denoting an intersection type (as long as the intersection is also a function type, typically meaning one element is a functional interface and the others are marker interfaces). Since the feature is generally useful, we also support casts of arbitrary expressions to intersection types.

5 Conversions and Contexts [Modified]

Compare JLS 5

Every expression written in the Java programming language either produces no result (15.1) or has a type that can be deduced at compile time (15.3). When an expression appears in most contexts, it must be compatible with a type expected in that context; this type is called the target type. For convenience, compatibility of an expression with its surrounding context is facilitated by the language in two ways:

If neither strategy is able to produce the appropriate type, an error occurs at compile time.

The rules determining whether an expression is a poly expression vary depending on the kind of context and the form of the expression. These rules are defined in Chapter 15, "Expressions." In addition to influencing the type of the expression, the target type may in some cases influence the run time behavior of the expression in order to produce a value of the appropriate type.

Similarly, the rules determining when a target type allows an implicit conversion vary depending on the kind of context, the type of the expression, and, in one special case, the value of a constant expression (15.29). A specific conversion from type S to type T allows an expression of type S to be treated at compile time as if it had type T instead. In some cases this will require a corresponding action at run time to check the validity of the conversion or to translate the run-time value of the expression into a form appropriate for the new type T.

[Example, list of conversion categories] ...

There are six kinds of conversion contexts in which poly expressions may be influenced by context or implicit conversions may occur. Each kind of context has different rules for poly expression typing and allows conversions in some of the categories above but not others. These contexts are:

The term "conversion" is also used to describe, without being specific, any conversions allowed in a particular context. For example, we say that an expression that is the initializer of a local variable is subject to "assignment conversion," meaning that a specific conversion will be implicitly chosen for that expression according to the rules for the assignment context.

Here are some examples of conversions occuring in various contexts.

[Example] ...

Discussion and motivation:
  1. Lambda expressions and method references may only appear in certain contexts, and their type and correctness are determined by this context. Other kinds of expressions in the existing language have already introduced dependencies on context, and this is a trend that seems likely to continue. Rather than treat each new feature in an ad-hoc manner, the introduction of poly expression and an explicit recognition that target types can influence expression types allows us to unify handling of context-dependent expressions under a single umbrella.

    Requiring an expression to denote the same thing in all contexts is useful, but limiting. In the past, the approach to the class of expressions that don't behave in this way has generally been to ignore them, as in statements like the following from the JLS 7 Chapter 5 introduction:

    "Every expression written in the Java programming language has a type that can be deduced from the structure of the expression and the types of the literals, variables, and methods mentioned in the expression."

  2. Deciding what contexts are allowed to support poly expressions is driven in large part by the practical need for such features:
    • Assignment contexts (including return statements, etc.) are supported, because the target type is easy to determine. In Java SE 7, this is the only context in which something like a poly expression got special treatment.
    • Invocation contexts are supported, because lambda expressions are a lot less useful if they can't be method arguments. This implies that overload resolution needs to be overhauled (see Part F)—the targeted method must be determined before knowing the type of each method argument. It would also be quite useful to allow nested polymorphic method invocations to be affected by the outer targeted method's signature.
    • Casting contexts are supported, because we want programmers to be able to use them to explicitly provide a lambda expression's target type. This is particularly useful when a class has overloaded methods with functional interface argument types that are similar but unrelated (FileFilter and Predicate<File>, say); it is also useful when a targeted type is not a functional interface, but it has a functional subinterface. However, generic method invocations are not poly expressions in casting contexts: the cast provides little useful information about the method's return type.
    • Numeric and boolean contexts (loop conditions, assert operands, binary expression operands) do not support poly expressions, because a lambda expression cannot target a primitive type, and working out proper inference for method invocations here would be quite complex without much payoff.
    • String contexts (such as "a" + exp) do not provide any useful information, because every value can be converted to a String.
    • The receiver in a method invocation, field access, etc. (exp.foo()) is not a poly expression because the target type is unknown—it would be impossible to enumerate every type that has a particular member (foo, in this case). There has been some interest in allowing inference to "chain": in a().b(), passing type information from the invocation of b to the invocation of a. This adds another dimension to the complexity of the inference algorithm, as partial information has to pass in both directions; it only works when the erasure of the return type of a() is fixed for all instantiations (e.g. List<T>). This feature would not fit very well into the poly expression model, since the target type cannot be easily derived.
    • The expression in an enhanced for loop is not in a poly context because, as the construct is currently defined, it is as if the expression were a receiver: exp.iterator() (or, in the array case, exp[i]). It is plausible that an Iterator could be wrapped as an Iterable in a for loop via a lambda expression (for (String s : () -> stringIterator)), but this doesn't mesh very well with the semantics of Iterable.
  3. A point of emphasis in this revision is to identify conversion contexts mainly as contexts rather than conversions, in order to generalize the way in which they are used both for typing of poly expressions and for generating implicit conversions. ("Assignment conversion context" was existing terminology, but "assignment conversion" was almost always used as shorthand.)

    The "promotion" terminology (meaning the inference of certain conversions that happen in numeric contexts) seems relatively minor, so "Conversions and Contexts" is used as a revised chapter title. "Conversions, Promotions, and Contexts" might also work.

  4. Many of the adjustments made to this section (for example, the distinction between strict and loose invocation contexts) are principally to provide a consistent framework for talking about type inference, covered in Part G.
  5. The old text outlined some (but not all) of the exceptions that could occur in particular contexts. This seemed unnecessarily detailed for a chapter introduction, so has been dropped. Similarly, the old text had a paragraph about numeric promotion that was unnecessarily detailed and no longer accurate.

5.2 Assignment Contexts [Modified]

Compare JLS 5.2

Assignment contexts allow the value of an expression to be assigned (15.26) to a variable: the type of the expression must be converted to the type of the variable. [jls-5.2-100]

...

If an expression is assignment-compatible with the type of a variable, we sometimes say the expression is assignable to the variable.

...

5.3 Method Invocation Contexts [Modified]

Compare JLS 5.3

Both strict invocation contexts and loose invocation contexts allow an argument value in a method or constructor invocation (8.8.7.1, 15.9, 15.12) to be assigned to a corresponding formal parameter. [jls-5.3-100]

Loose invocation contexts allow a more permissive set of conversions; they are only used for a particular invocation if no applicable declaration can be found using strict invocation contexts. [jsr335-5.3-101]

Both invocation contexts allow the use of one of the following: [jls-5.3-110]

If, after the conversions listed above have been applied, the resulting type is a raw type (4.8), an unchecked conversion (5.1.9) may then be applied. [jls-5.3-120]

It is a compile-time error if the chain of conversions contains two parameterized types that are not in the subtype relation. [jls-5.3-200]

A value of the null type (the null reference is the only such value) may be converted to any reference type. [jls-5.3-300]

In addition, a loose invocation context allows the use of one of the following: [jls-5.3-110']

If the type of the expression cannot be converted to the type of the parameter by a conversion permitted in a loose method invocation context, then a compile-time error occurs. [jls-5.3-310]

...

By design, neither strict nor loose invocation contexts include the implicit narrowing of integer constants which is allowed in assignment contexts (5.2). The designers of the Java programming language felt that ...

...

5.4 String Contexts [Modified]

Compare JLS 5.4

String contexts apply only to an operand of the binary + operator which is not a String when the other operand is a String. [jls-5.4-100]

The target type in these contexts is always String, and a string conversion (5.1.11) always occurs; evaluation of the + operator then proceeds as specified in 15.8.1. [jls-5.4-110]

5.5 Casting Contexts [Modified]

Compare JLS 5.5

Casting contexts allow the operand of a cast operator (15.16) to be converted to the type explicitly named by the cast operator. [jls-5.5-100]

...

5.5.1 Reference Type Casting [Modified]

Compare JLS 5.5.1

Given a compile-time reference type S (source) and a compile-time reference type T (target), a casting conversion exists from S to T if no compile-time errors occur due to the following rules. [jls-5.5.1-100]

If S is a class type: [jls-5.5.1-200]

If S is an interface type: [jls-5.5.1-300]

If S is a type variable, then this algorithm is applied recursively, using the upper bound of S in place of S. [jls-5.5.1-400]

If S is an intersection type, A1 & ... & An, then it is a compile-time error if there exists an Ai (1 ≤ i ≤ n) such that S Ai cannot be cast to Ai T by this algorithm. That is, the success of the cast is determined by the most restrictive component of the intersection type. [jls-5.5.1-500]

If S is an array type SC[], that is, an array of components of type SC: [jls-5.5.1-600]

Discussion and motivation:
  1. Casts can be used to explicitly "tag" a lambda expression or a method reference with a particular target type. To provide an appropriate degree of flexibility, the target type may be a list of types denoting an intersection type (as long as the intersection is also a functional interface type (9.8)). Since the feature is generally useful, these rules also support casts of arbitrary expressions to intersection types.

5.5.2 Checked Casts and Unchecked Casts [Modified]

Compare JLS 5.5.2

A cast from a type S to a type T is statically known to be correct if and only if S <: T (4.10). [jls-5.5.2-100]

A cast from a type S to a parameterized type (4.5) is unchecked unless ... [jls-5.5.2-200]

A cast from a type S to a type variable T is unchecked unless S <: T. [jls-5.5.2-300]

A cast from a type S to an intersection type T1 & ... & Tn is unchecked if there exists a Ti (1 ≤ i ≤ n) such that a cast from S to Ti is unchecked. [jsr335-5.5.2-350]

An unchecked cast from S to a non-intersection type T is completely unchecked if the cast from |S| to |T| is statically known to be correct. Otherwise, it is partially unchecked. [jls-5.5.2-400]

An unchecked cast from S to an intersection type T1 & ... & Tn is completely unchecked if, for all i, 1 ≤ i ≤ n, a cast from S to Ti is either statically known to be correct or completely unchecked. Otherwise, it is partially unchecked. [jsr335-5.5.2-410]

An unchecked cast causes a compile-time unchecked warning, unless suppressed by the SuppressWarnings annotation (9.6.3.5). [jls-5.5.2-500]

A cast is checked if it is not statically known to be correct and it is not unchecked. [jls-5.5.2-600]

If a cast to a reference type is not a compile-time error, there are several cases: [jls-5.5.2-700]

5.6 Numeric Contexts [Modified]

Compare JLS 5.6

Numeric contexts apply to the operands of an arithmetic operator. [jls-5.6-100]

Numeric promotion contexts allow the use of: [jls-5.6-110]

A numeric promotion is a process by which, given a numeric operator and its argument expressions, the arguments are converted to an inferred target type T. T is chosen during promotion such that each argument expression can be converted to T and the operation is defined for values of type T. [jls-5.6-200]

The two kinds of numeric promotion are unary numeric promotion (5.6.1) and binary numeric promotion (5.6.2).

15.1 Evaluation, Denotation, and Result [Addendum]

See JLS 15.1

The following from section 15.2 is merged into 15.1, in order to make room for a new section.

If an expression denotes a variable, and a value is required for use in further evaluation, then the value of that variable is used. In this context, if the expression denotes a variable or a value, we may speak simply of the value of the expression. [jls-15.2-100]

If the value of a variable of type float or double is used in this manner, then value set conversion (5.1.13) is applied to the value of the variable. [jls-15.2-110]

15.2 Forms of Expressions [New]

Expressions can be broadly categorized into one of the following syntactic forms:

Precedence among operators is managed by a hierarchy of grammar productions. The lowest-precedence operator is the assignment operator, so all expressions are syntactically included in the AssignmentExpression nonterminal (15.26):

Expression:
  AssignmentExpression
This production was moved from 15.27.

When some expressions appear in certain contexts (5), they are considered poly expressions. Expressions that are not poly expressions are standalone expressions. [jsr335-15.2-10]

The following forms of expressions may be poly expressions: [jsr335-15.2-12]

The rules determining whether an expression of one of these forms is a poly expression are explained separately below for each form of expression. [jsr335-15.2-11]

All other forms of expressions are said to have a standalone form, and are always standalone expressions. [jsr335-15.2-13]

Some expressions have a value that can be determined at compile time. These are constant expressions (15.29). [jsr335-15.2-20]

Discussion and motivation:
  1. This new section is a preview of the rest of the chapter. Its most important role is to describe the kinds of poly expressions, but more generally it's a convenient place to outline the different approaches to categorizing expressions. It also gives an up front definition of the term expression (this previously was withheld until the very end of the chapter).
  2. Special collection literal syntax is an anticipated future Java feature. These would fit in nicely as poly expressions (and perhaps as standalone expressions, too, like method invocations).

    Array initializers (int[] x = {1,2,3}; see 10.6, 14.4) are not expressions at all, as a sort of hack in the original language design to allow a very restricted context dependency. (Despite this, the initializer of a variable is often refered to generically as an "expression.") It would make sense to take advantage of poly expressions as a way to eliminate this special case, bring them under the same umbrella, and allow more flexibility in their use. But this would best be addressed in conjunction with any new collection syntax.


15.3 Type of an Expression [Modified]

Compare JLS 15.3

If an expresion denotes a variable or a value, then the expression has a type known at compile time.

Given certain meanings of names (6.5), the type of a standalone expression can be determined entirely from the contents of the expression. In contrast, the type of a poly expression may be influenced by the expression's target type (5).

The rules for determining the type of an expression are explained separately below for each kind of expression.

...

15.8 Primary Expressions [Modified]

Compare JLS 15.8

...

Primary:
  PrimaryNoNewArray
  ArrayCreationExpression

PrimaryNoNewArray:
  Literal
  Type '.' 'class'
  'void' '.' 'class'
  'this'
  ClassName '.' 'this'
  '(' Expression ')'
  ClassInstanceCreationExpression
  FieldAccess
  MethodInvocation
  ArrayAccess
  LambdaExpression
  MethodReference

...

Discussion and motivation:
  1. Despite strict restrictions on where they can appear, lambda expressions and method references are most naturally described as primaries. The restrictions on context are therefore enforced outside the grammar.
  2. Given this grammar, the most appropriate place to define lambda expressions, etc., is after section 15.13. However, to avoid undue renumbering, the new sections are introduced at the end of the chapter instead (15.27, 15.28).

15.8.5 Parenthesized Expressions [Addendum]

See JLS 15.8.5

If a parenthesized expression appears in a context of a particular kind with target type T (5), its contained expression similarly appears in a context of the same kind with target type T. [jsr335-15.8.5-10]

If the contained expression is a poly expression (15.2), the parenthesized expression is also a poly expression. Otherwise, it is a standalone expression. [jsr335-15.8.5-20]

15.9 Class Instance Creation Expressions [Addendum]

See JLS 15.9

A class instance creation expression is a poly expression (15.2) if i) it uses a diamond '<>' in place of type arguments, and ii) it appears in an assignment context (5.2) or an invocation context (5.3). Otherwise, it is a standalone expression. [jsr335-15.9-10]

15.12 Method Invocation Expressions [Addendum]

See JLS 15.12

A method invocation expression is a poly expression if all of the following are true: [jsr335-15.12-10]

Otherwise, the method invocation expression is a standalone expression. [jsr335-15.12-11]

Discussion and motivation:
  1. Class instance creations and method invocations may be poly expressions because type argument inference (including "diamond" inference) may depend on context.

    Given that adjustments must be made to inference in order to support lambda expressions, this document also seeks to improve the algorithm's interaction with context in general, which is currently very ad hoc. See Part G for details.

15.16 Cast Expressions [Modified]

Compare JLS 15.16

A cast expression converts, at run-time, a value of one numeric type to a similar value of another numeric type; or confirms, at compile-time, that the type of an expression is boolean; or checks, at run-time, that a reference value refers to an object whose class is compatible with a specified reference type or list of reference types.

The parentheses and the type or list of types they contain are sometimes called the cast operator.

CastExpression:
  '(' PrimitiveType ')' UnaryExpression
  '(' ReferenceType AdditionalBoundListopt ')' UnaryExpressionNotPlusMinus

See 15.15 for a discussion of the distinction between UnaryExpression and UnaryExpressionNotPlusMinus.

The following productions from 4.4 are repeated here for convenience:

AdditionalBoundList:
  AdditionalBound AdditionalBoundList
  AdditionalBound

AdditionalBound:
  '&' InterfaceType

The target type for the casting context (5.5) introduced by the cast expression is, when an AdditionalBoundList is absent, the type appearing within the parentheses; or, when an AdditionalBoundList is present, the intersection type expressed by ReferenceType AdditionalBoundList. [jsr335-15.16-10]

The type of a cast expression is the result of applying capture conversion (5.1.10) to this target type. [jls-15.16-200]

15.25 Conditional Operator ? : [Modified]

Compare JLS 15.25

...

The conditional operator has three operand expressions. ? appears between the first and second expressions, and : appears between the second and third expressions. [jls-15.25-200]

The first expression must be of type boolean or Boolean, or a compile-time error occurs. [jls-15.25-210]

It is a compile-time error for either the second or the third operand expression to be an invocation of a void method. [jls-15.25-220]

There are three kinds of conditional expressions, classified according to the second and third operand expressions: boolean conditional expressions, numeric conditional expressions, and reference conditional expressions. [jsr335-15.25-10]

The process for determining the type of a conditional expression depends on the kind of conditional, as outlined in the following sections. [jls-15.25-300]

At run-time, the first operand expression of the conditional expression is evaluated first. If necessary, unboxing conversion is performed on the result. [jls-15.25-400]

The resulting boolean value is then used to choose either the second or the third operand expression: [jls-15.25-410]

The chosen operand expression is then evaluated and the resulting value is converted to the type of the conditional expression as determined by the rules stated above below. [jls-15.25-420]

This conversion may include boxing (5.1.7) or unboxing (5.1.8) conversion. [jls-15.25-430]

The operand expression not chosen is not evaluated for that particular evaluation of the conditional expression. [jls-15.25-440]

Discussion and motivation:
  1. We classify conditional expressions here in order to enhance the typing rules of reference conditionals (15.25.3) while preserving existing behavior of boolean and numeric conditionals. If we tried to treat all conditionals uniformly, there would be a variety of unwanted incompatible changes, including changes in overload resolution and boxing/unboxing behavior.

    This also simplifies the presentation of the typing rules by separating the rules for handling primitives from the rules for handling references.

15.25.1 Boolean Conditional Expressions [New]

Boolean conditional expressions are standalone expressions (15.2). [jsr335-15.25.1-10]

The type of a boolean conditional expression is determined as follows: [jsr335-15.25.1-20]

15.25.2 Numeric Conditional Expressions [New]

Numeric conditional expressions are standalone expressions (15.2). [jsr335-15.25.2-10]

The type of a numeric conditional expression is determined as follows: [jsr335-15.25.2-20]

The typing rules are lifted directly from the relevant parts of 15.25.

15.25.3 Reference Conditional Expressions [New]

A reference conditional expression is a poly expression if it appears in an assignment context (5.2) or an invocation context (5.3). Otherwise, it is a standalone expression. [jsr335-15.25.3-10]

Where a poly reference conditional expression appears in a context of a particular kind with target type T (5), its second and third operand expressions similarly appear in a context of the same kind with target type T. [jsr335-15.25.3-20]

The type of a poly reference conditional expression is the same as its target type. [jsr335-15.25.3-30]

The type of a standalone reference conditional expression is determined as follows: [jsr335-15.25.3-35]

The standalone typing rules are lifted directly from the relevant parts of 15.25.
Discussion and motivation:
  1. By making conditional operator expressions poly expressions, we allow them to "pass down" context to their arguments. This allows lambda expressions and method references to appear as subexpressions.
    return condition ? (x -> x) : (x -> -x);

    It also allows us to use the extra information to improve type checking of generic method invocations. In Java SE 7, this is well-typed:

    List<String> l = Arrays.asList();

    While this is not:

    List<String> l = condition ? Arrays.asList() : Arrays.asList("a","b");

    These rules allow both expressions to be considered well-typed.

15.29 Constant Expression [New]

This section is renumbered from 15.28 to make room for Method Reference Expressions (15.28).