Skip to content

Commit

Permalink
Implement reading from null safe dereferences
Browse files Browse the repository at this point in the history
Null safe dereferences make handling null or missing values shorter.
Compare without:
```
if (ctx._source.missing != null && ctx._source.missing.foo != null) {
  ctx._source.foo_length = ctx.source.missing.foo.length()
}
```

To with:
```
Integer length = ctx._source.missing?.foo?.length();
if (length != null) {
  ctx._source.foo_length = length
}
```

Combining this with the as of yet unimplemented elvis operator allows
for very concise defaults for nulls:
```
ctx._source.foo_length = ctx._source.missing?.foo?.length() ?: 0;
```

Since you have to start somewhere, we started with null safe dereferenes.

Anyway, this is a feature borrowed from groovy. Groovy allows writing to
null values like:
```
def v = null
v?.field = 'cat'
```
And the writes are simply ignored. Painless doesn't support this at this
point because it'd be complex to implement and maybe not all that useful.

There is no runtime cost for this feature if it is not used. When it is
used we implement it fairly efficiently, adding a jump rather than a
temporary variable.

This should also work fairly well with doc values.
  • Loading branch information
nik9000 committed Nov 9, 2016
1 parent b743ab0 commit d03b8e4
Show file tree
Hide file tree
Showing 13 changed files with 697 additions and 293 deletions.
31 changes: 31 additions & 0 deletions docs/reference/modules/scripting/painless-syntax.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,37 @@ using these characters:
|`x` | COMMENTS (aka extended) | `'a' ==~ /a #comment/x`
|=======================================================================

[float]
[[painless-deref]]
=== Dereferences

Like lots of languages, Painless uses `.` to reference fields and call methods:

[source,painless]
---------------------------------------------------------
String foo = 'foo';
TypeWithGetterOrPublicField bar = new TypeWithGetterOrPublicField()
return foo.length() + bar.x
---------------------------------------------------------

Like Groovy, Painless uses `?.` to perform null-safe references, with the
result being `null` if the left hand side is null:

[source,painless]
---------------------------------------------------------
String foo = null;
return foo?.length() // Returns null
---------------------------------------------------------

Unlike Groovy, Painless doesn't support writing to null values with this
operator:

[source,painless]
---------------------------------------------------------
TypeWithSetterOrPublicField foo = null;
foo?.x = 'bar' // Compile error
---------------------------------------------------------

[float]
[[painless-operators]]
=== Operators
Expand Down
4 changes: 2 additions & 2 deletions modules/lang-painless/src/main/antlr/PainlessParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ postdot
;

callinvoke
: DOT DOTID arguments
: COND? DOT DOTID arguments
;

fieldaccess
: DOT ( DOTID | DOTINTEGER )
: COND? DOT ( DOTID | DOTINTEGER )
;

braceaccess
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
public final class AnalyzerCaster {

public static Cast getLegalCast(Location location, Type actual, Type expected, boolean explicit, boolean internal) {
if (actual == null || expected == null) {
throw new IllegalStateException("Neither actual [" + actual + "] nor expected [" + expected + "] can be null");
}
if (actual.equals(expected)) {
return null;
}
Expand Down
Loading

0 comments on commit d03b8e4

Please sign in to comment.