Skip to content

Commit

Permalink
Merge: Safe call operator
Browse files Browse the repository at this point in the history
A long time ago, there was a proposal to have a safe call operator in Nit nitlang#1274 `x?.foo` that executes the call if `x` is not null and returns null otherwise (instead of aborting).

This was refused because at the time, the syntax `x?.foo` was considered weird and not POLA.
Moreover, what was proposed was a more general version of the concept that could be used everywhere, not only as a receiver that made the semantic quite complex and even less POLA.

Nowadays, most languages have adopted it https://en.wikipedia.org/wiki/Safe_navigation_operator so the syntax and behavior is not as weird as before.

This operator is nice in the context of Nit with nullable types since if avoids to use chained `if x != null then x.foo` and plays nicely with the type system.

Currently, only explicit dotted calls are handled; e.g. `x?.foo`, I'm not sure if something should be done with other call constructions like `x + y` or `x[y]`.

Pull-Request: nitlang#2761
Reviewed-by: Lucas Bajolet <[email protected]>
Reviewed-by: Alexandre Terrasa <[email protected]>
  • Loading branch information
privat committed Jul 18, 2019
2 parents 093e77e + a3e6523 commit 17ea05c
Show file tree
Hide file tree
Showing 19 changed files with 19,713 additions and 14,531 deletions.
23 changes: 22 additions & 1 deletion src/compiler/abstract_compiler.nit
Original file line number Diff line number Diff line change
Expand Up @@ -4085,10 +4085,24 @@ redef class ASendExpr
redef fun expr(v)
do
var recv = v.expr(self.n_expr, null)
if is_safe then
v.add "if ({recv}!=NULL) \{"
end
var callsite = self.callsite.as(not null)
if callsite.is_broken then return null
var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.raw_arguments)
return v.compile_callsite(callsite, args)
var res = v.compile_callsite(callsite, args)
if is_safe then
if res != null then
var orig_res = res
res = v.new_var(self.mtype.as(not null))
v.add("{res} = {orig_res};")
v.add("\} else \{")
v.add("{res} = NULL;")
end
v.add("\}")
end
return res
end
end

Expand Down Expand Up @@ -4256,6 +4270,13 @@ redef class AVarargExpr
end
end

redef class ASafeExpr
redef fun expr(v)
do
return v.expr(self.n_expr, null)
end
end

redef class ANamedargExpr
redef fun expr(v)
do
Expand Down
31 changes: 24 additions & 7 deletions src/interpreter/naive_interpreter.nit
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,10 @@ abstract class Instance
# Abort if the instance is not a boolean value.
fun is_true: Bool do abort

# Return `true` if the instance is null.
# Return `false` otherwise.
fun is_null: Bool do return mtype isa MNullType

# Return true if `self` IS `o` (using the Nit semantic of is)
fun eq_is(o: Instance): Bool do return self.is_same_instance(o)

Expand Down Expand Up @@ -1529,7 +1533,7 @@ redef class AAttrPropdef
else if mpropdef == mwritepropdef then
assert args.length == 2
var arg = args[1]
if is_optional and arg.mtype isa MNullType then
if is_optional and arg.is_null then
var f = v.new_frame(self, mpropdef, args)
arg = evaluate_expr(v, recv, f)
end
Expand Down Expand Up @@ -1824,7 +1828,7 @@ redef class AForExpr
for g in n_groups do
var col = v.expr(g.n_expr)
if col == null then return
if col.mtype isa MNullType then fatal(v, "Receiver is null")
if col.is_null then fatal(v, "Receiver is null")

var iter = v.callsite(g.method_iterator, [col]).as(not null)
iters.add iter
Expand Down Expand Up @@ -2185,7 +2189,7 @@ redef class AAsNotnullExpr
do
var i = v.expr(self.n_expr)
if i == null then return null
if i.mtype isa MNullType then
if i.is_null then
fatal(v, "Cast failed")
end
return i
Expand Down Expand Up @@ -2218,6 +2222,12 @@ redef class ASendExpr
do
var recv = v.expr(self.n_expr)
if recv == null then return null

# Safe call shortcut if recv is null
if is_safe and recv.is_null then
return recv
end

var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.raw_arguments)
if args == null then return null

Expand Down Expand Up @@ -2314,7 +2324,7 @@ redef class AAttrExpr
do
var recv = v.expr(self.n_expr)
if recv == null then return null
if recv.mtype isa MNullType then fatal(v, "Receiver is null")
if recv.is_null then fatal(v, "Receiver is null")
var mproperty = self.mproperty.as(not null)
return v.read_attribute(mproperty, recv)
end
Expand All @@ -2325,7 +2335,7 @@ redef class AAttrAssignExpr
do
var recv = v.expr(self.n_expr)
if recv == null then return
if recv.mtype isa MNullType then fatal(v, "Receiver is null")
if recv.is_null then fatal(v, "Receiver is null")
var i = v.expr(self.n_value)
if i == null then return
var mproperty = self.mproperty.as(not null)
Expand All @@ -2338,7 +2348,7 @@ redef class AAttrReassignExpr
do
var recv = v.expr(self.n_expr)
if recv == null then return
if recv.mtype isa MNullType then fatal(v, "Receiver is null")
if recv.is_null then fatal(v, "Receiver is null")
var value = v.expr(self.n_value)
if value == null then return
var mproperty = self.mproperty.as(not null)
Expand All @@ -2354,7 +2364,7 @@ redef class AIssetAttrExpr
do
var recv = v.expr(self.n_expr)
if recv == null then return null
if recv.mtype isa MNullType then fatal(v, "Receiver is null")
if recv.is_null then fatal(v, "Receiver is null")
var mproperty = self.mproperty.as(not null)
return v.bool_instance(v.isset_attribute(mproperty, recv))
end
Expand All @@ -2367,6 +2377,13 @@ redef class AVarargExpr
end
end

redef class ASafeExpr
redef fun expr(v)
do
return v.expr(self.n_expr)
end
end

redef class ANamedargExpr
redef fun expr(v)
do
Expand Down
83 changes: 49 additions & 34 deletions src/parser/lexer.nit
Original file line number Diff line number Diff line change
Expand Up @@ -1170,7 +1170,7 @@ redef class TBang
end
end

redef class TAt
redef class TQuest
redef fun parser_index: Int
do
return 97
Expand All @@ -1182,7 +1182,7 @@ redef class TAt
end
end

redef class TSemi
redef class TAt
redef fun parser_index: Int
do
return 98
Expand All @@ -1194,7 +1194,7 @@ redef class TSemi
end
end

redef class TClassid
redef class TSemi
redef fun parser_index: Int
do
return 99
Expand All @@ -1206,7 +1206,7 @@ redef class TClassid
end
end

redef class TId
redef class TClassid
redef fun parser_index: Int
do
return 100
Expand All @@ -1218,7 +1218,7 @@ redef class TId
end
end

redef class TAttrid
redef class TId
redef fun parser_index: Int
do
return 101
Expand All @@ -1230,7 +1230,7 @@ redef class TAttrid
end
end

redef class TInteger
redef class TAttrid
redef fun parser_index: Int
do
return 102
Expand All @@ -1242,7 +1242,7 @@ redef class TInteger
end
end

redef class TFloat
redef class TInteger
redef fun parser_index: Int
do
return 103
Expand All @@ -1254,7 +1254,7 @@ redef class TFloat
end
end

redef class TString
redef class TFloat
redef fun parser_index: Int
do
return 104
Expand All @@ -1266,7 +1266,7 @@ redef class TString
end
end

redef class TStartString
redef class TString
redef fun parser_index: Int
do
return 105
Expand All @@ -1278,7 +1278,7 @@ redef class TStartString
end
end

redef class TMidString
redef class TStartString
redef fun parser_index: Int
do
return 106
Expand All @@ -1290,7 +1290,7 @@ redef class TMidString
end
end

redef class TEndString
redef class TMidString
redef fun parser_index: Int
do
return 107
Expand All @@ -1302,7 +1302,7 @@ redef class TEndString
end
end

redef class TChar
redef class TEndString
redef fun parser_index: Int
do
return 108
Expand All @@ -1314,7 +1314,7 @@ redef class TChar
end
end

redef class TBadString
redef class TChar
redef fun parser_index: Int
do
return 109
Expand All @@ -1326,7 +1326,7 @@ redef class TBadString
end
end

redef class TBadTString
redef class TBadString
redef fun parser_index: Int
do
return 110
Expand All @@ -1338,7 +1338,7 @@ redef class TBadTString
end
end

redef class TBadChar
redef class TBadTString
redef fun parser_index: Int
do
return 111
Expand All @@ -1350,7 +1350,7 @@ redef class TBadChar
end
end

redef class TExternCodeSegment
redef class TBadChar
redef fun parser_index: Int
do
return 112
Expand All @@ -1362,7 +1362,7 @@ redef class TExternCodeSegment
end
end

redef class TBadExtern
redef class TExternCodeSegment
redef fun parser_index: Int
do
return 113
Expand All @@ -1374,11 +1374,23 @@ redef class TBadExtern
end
end

redef class TBadExtern
redef fun parser_index: Int
do
return 114
end

init init_tk(loc: Location)
do
_location = loc
end
end


redef class EOF
redef fun parser_index: Int
do
return 114
return 115
end
end

Expand Down Expand Up @@ -1677,54 +1689,57 @@ redef class Lexer
return new TBang.init_tk(location)
end
if accept_token == 98 then
return new TAt.init_tk(location)
return new TQuest.init_tk(location)
end
if accept_token == 99 then
return new TSemi.init_tk(location)
return new TAt.init_tk(location)
end
if accept_token == 100 then
return new TClassid.init_tk(location)
return new TSemi.init_tk(location)
end
if accept_token == 101 then
return new TId.init_tk(location)
return new TClassid.init_tk(location)
end
if accept_token == 102 then
return new TAttrid.init_tk(location)
return new TId.init_tk(location)
end
if accept_token == 103 then
return new TInteger.init_tk(location)
return new TAttrid.init_tk(location)
end
if accept_token == 104 then
return new TFloat.init_tk(location)
return new TInteger.init_tk(location)
end
if accept_token == 105 then
return new TString.init_tk(location)
return new TFloat.init_tk(location)
end
if accept_token == 106 then
return new TStartString.init_tk(location)
return new TString.init_tk(location)
end
if accept_token == 107 then
return new TMidString.init_tk(location)
return new TStartString.init_tk(location)
end
if accept_token == 108 then
return new TEndString.init_tk(location)
return new TMidString.init_tk(location)
end
if accept_token == 109 then
return new TChar.init_tk(location)
return new TEndString.init_tk(location)
end
if accept_token == 110 then
return new TBadString.init_tk(location)
return new TChar.init_tk(location)
end
if accept_token == 111 then
return new TBadTString.init_tk(location)
return new TBadString.init_tk(location)
end
if accept_token == 112 then
return new TBadChar.init_tk(location)
return new TBadTString.init_tk(location)
end
if accept_token == 113 then
return new TExternCodeSegment.init_tk(location)
return new TBadChar.init_tk(location)
end
if accept_token == 114 then
return new TExternCodeSegment.init_tk(location)
end
if accept_token == 115 then
return new TBadExtern.init_tk(location)
end
abort # unknown token index `accept_token`
Expand Down
Loading

0 comments on commit 17ea05c

Please sign in to comment.