Merge: new `with` statement
authorJean Privat <jean@pryen.org>
Thu, 9 Apr 2015 05:24:37 +0000 (12:24 +0700)
committerJean Privat <jean@pryen.org>
Thu, 9 Apr 2015 05:24:37 +0000 (12:24 +0700)
Introduce the `with` statement that is analogous to the `with` of Python or the `uning` of C#.

~~~nit
with x = something do
   x.work
end
~~~

That is equivalent with

~~~nit
do
   var x = something
   x.start
   x.work
   x.finish
end
~~~

Note that an alternative syntax, without the local variable exists:

~~~nit
with something do
   work
end
~~~

that is used when we want some automatic control or whatever. Eg resource allocation or critical section.

~~~nit
do
   var tmp = something
   tmp.start
   work
   tmp.stop
end
~~~

Most real-world examples could be

~~~nit
with file = path.create do
   file.write("Bla")
end
~~~

and

~~~nit
with a_mutex do
   work
end
~~~

## Names

I reused the `finish` method from Iterator.
I introduced the antonym `start` for the other side.

Note that the `start` is important because it helps the object to know it has to prepare itself for a `finish`.

I did not introduce an interface, mainly because I have no idea for a name. C# names it `IDisposable` and the `finish` method is called `Dispose`. There is no `start` because C# designers are lunatic sometime.

In python there is no interface (obviously) and the method are called
`__enter__` and `__exit__` because Python likes identifiers that resemble words eaten by boas.
Note that the returned variable is not the original expression but the result of `__enter__`.
With the Python semantic, but the Nit syntax, the first example will be equivalent to

~~~nit
do
   var tmp = something
   var x = tmp.start
   x.work
   x.finish
end
~~~

I am not sure of the benefits of the additional complexity, but the the more I thing about it the more I think this could be useful, ex for the control case the `start` can return the handler to `finish`.

The issue is that proposed syntax: `with x = something` does not make sense (because `tmp=something` and `x=tmp.start`) thus should be replaced with something else; Python uses `with something as x:` that we can reuse but I do not want because it is ugly.

An other alternative I tough of was to reuse the `for` keyword. but It is convoluted.
The two previous real world examples will then be

~~~nit
for file = path.create do
   file.write("Bla")
end
~~~

and

~~~nit
for a_mutex do
   work
end
~~~

Pull-Request: #1243
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Etienne M. Gagnon <egagnon@j-meg.com>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

1  2 
src/interpreter/naive_interpreter.nit
src/semantize/typing.nit

@@@ -1016,9 -1016,6 +1016,9 @@@ redef class AMethPropde
                                return v.int_instance(res)
                        else if pname == "atof" then
                                return v.float_instance(recvval.to_f)
 +                      else if pname == "fast_cstring" then
 +                              var ns = recvval.to_cstring.to_s.substring_from(args[1].to_i)
 +                              return v.native_string_instance(ns)
                        end
                else if cname == "String" then
                        var cs = v.send(v.force_get_primitive_method("to_cstring", args.first.mtype), [args.first])
@@@ -1273,14 -1270,16 +1273,16 @@@ redef class ABlockExp
  end
  
  redef class AVardeclExpr
-       redef fun stmt(v)
+       redef fun expr(v)
        do
                var ne = self.n_expr
                if ne != null then
                        var i = v.expr(ne)
-                       if i == null then return
+                       if i == null then return null
                        v.write_variable(self.variable.as(not null), i)
+                       return i
                end
+               return null
        end
  end
  
@@@ -1465,6 -1464,19 +1467,19 @@@ redef class AForExp
        end
  end
  
+ redef class AWithExpr
+       redef fun stmt(v)
+       do
+               var expr = v.expr(self.n_expr)
+               if expr == null then return
+               v.callsite(method_start, [expr])
+               v.stmt(self.n_block)
+               v.is_escape(self.break_mark) # Clear the break
+               v.callsite(method_finish, [expr])
+       end
+ end
  redef class AAssertExpr
        redef fun stmt(v)
        do
diff --combined src/semantize/typing.nit
@@@ -115,12 -115,7 +115,12 @@@ private class TypeVisito
                        #node.debug("Unsafe typing: expected {sup}, got {sub}")
                        return sup
                end
 -              self.modelbuilder.error(node, "Type error: expected {sup}, got {sub}")
 +              if sub.need_anchor then
 +                      var u = anchor_to(sub)
 +                      self.modelbuilder.error(node, "Type error: expected {sup}, got {sub}: {u}")
 +              else
 +                      self.modelbuilder.error(node, "Type error: expected {sup}, got {sub}")
 +              end
                return null
        end
  
                return sup
        end
  
 +      # Can `mtype` be null (up to the current knowledge)?
 +      fun can_be_null(mtype: MType): Bool
 +      do
 +              if mtype isa MNullableType or mtype isa MNullType then return true
 +              if mtype isa MFormalType then
 +                      var x = anchor_to(mtype)
 +                      if x isa MNullableType or x isa MNullType then return true
 +              end
 +              return false
 +      end
 +
 +      # Check that `mtype` can be null (up to the current knowledge).
 +      #
 +      # If not then display a `useless-null-test` warning on node and return false.
 +      # Else return true.
 +      fun check_can_be_null(anode: ANode, mtype: MType): Bool
 +      do
 +              if can_be_null(mtype) then return true
 +
 +              if mtype isa MFormalType then
 +                      var res = anchor_to(mtype)
 +                      modelbuilder.warning(anode, "useless-null-test", "Warning: expression is not null, since it is a `{mtype}: {res}`.")
 +              else
 +                      modelbuilder.warning(anode, "useless-null-test", "Warning: expression is not null, since it is a `{mtype}`.")
 +              end
 +              return false
 +      end
 +
        # Special verification on != and == for null
        # Return true
        fun null_test(anode: ABinopExpr)
  
                if not mtype2 isa MNullType then return
  
 +              if mtype isa MNullType then return
 +
                # Check of useless null
 -              if not mtype isa MNullableType then
 -                      if not anchor_to(mtype) isa MNullableType then
 -                              modelbuilder.warning(anode, "useless-null-test", "Warning: expression is not null, since it is a `{mtype}`.")
 -                      end
 -                      return
 -              end
 +              if not check_can_be_null(anode.n_expr, mtype) then return
 +
 +              mtype = mtype.as_notnull
  
                # Check for type adaptation
                var variable = anode.n_expr.its_variable
                if variable == null then return
  
 +              # One is null (mtype2 see above) the other is not null
                if anode isa AEqExpr then
                        anode.after_flow_context.when_true.set_var(variable, mtype2)
 -                      anode.after_flow_context.when_false.set_var(variable, mtype.mtype)
 +                      anode.after_flow_context.when_false.set_var(variable, mtype)
                else if anode isa ANeExpr then
                        anode.after_flow_context.when_false.set_var(variable, mtype2)
 -                      anode.after_flow_context.when_true.set_var(variable, mtype.mtype)
 +                      anode.after_flow_context.when_true.set_var(variable, mtype)
                else
                        abort
                end
  
        fun get_mclass(node: ANode, name: String): nullable MClass
        do
 -              var mclass = modelbuilder.try_get_mclass_by_name(node, mmodule, name)
 -              if mclass == null then
 -                      self.modelbuilder.error(node, "Type Error: missing primitive class `{name}'.")
 -              end
 +              var mclass = modelbuilder.get_mclass_by_name(node, mmodule, name)
                return mclass
        end
  
                if recvtype isa MNullType then
                        # `null` only accepts some methods of object.
                        if name == "==" or name == "!=" or name == "is_same_instance" then
 -                              unsafe_type = mmodule.object_type.as_nullable
 +                              var objclass = get_mclass(node, "Object")
 +                              if objclass == null then return null # Forward error
 +                              unsafe_type = objclass.mclass_type
                        else
                                self.error(node, "Error: Method '{name}' call on 'null'.")
                                return null
                end
  
  
 -              var msignature = mpropdef.new_msignature or else mpropdef.msignature.as(not null)
 +              var msignature = mpropdef.new_msignature or else mpropdef.msignature
 +              if msignature == null then return null # skip error
                msignature = resolve_for(msignature, recvtype, recv_is_self).as(MSignature)
  
                var erasure_cast = false
                var rettype = mpropdef.msignature.return_mtype
                if not recv_is_self and rettype != null then
 -                      rettype = rettype.as_notnullable
 +                      rettype = rettype.undecorate
                        if rettype isa MParameterType then
                                var erased_rettype = msignature.return_mtype
                                assert erased_rettype != null
                        var found = true
                        for t2 in col do
                                if t2 == null then continue # return null
 -                              if t2 isa MNullableType or t2 isa MNullType then
 +                              if can_be_null(t2) and not can_be_null(t1) then
                                        t1 = t1.as_nullable
                                end
                                if not is_subtype(t2, t1) then found = false
@@@ -602,18 -569,14 +602,18 @@@ redef class AMethPropde
                var nblock = self.n_block
                if nblock == null then return
  
 -              var mpropdef = self.mpropdef.as(not null)
 +              var mpropdef = self.mpropdef
 +              if mpropdef == null then return # skip error
 +
                var v = new TypeVisitor(modelbuilder, mpropdef.mclassdef.mmodule, mpropdef)
                self.selfvariable = v.selfvariable
  
                var mmethoddef = self.mpropdef.as(not null)
 -              for i in [0..mmethoddef.msignature.arity[ do
 -                      var mtype = mmethoddef.msignature.mparameters[i].mtype
 -                      if mmethoddef.msignature.vararg_rank == i then
 +              var msignature = mmethoddef.msignature
 +              if msignature == null then return # skip error
 +              for i in [0..msignature.arity[ do
 +                      var mtype = msignature.mparameters[i].mtype
 +                      if msignature.vararg_rank == i then
                                var arrayclass = v.get_mclass(self.n_signature.n_params[i], "Array")
                                if arrayclass == null then return # Skip error
                                mtype = arrayclass.get_mtype([mtype])
                end
                v.visit_stmt(nblock)
  
 -              if not nblock.after_flow_context.is_unreachable and mmethoddef.msignature.return_mtype != null then
 +              if not nblock.after_flow_context.is_unreachable and msignature.return_mtype != null then
                        # We reach the end of the function without having a return, it is bad
                        v.error(self, "Control error: Reached end of function (a 'return' with a value was expected).")
                end
@@@ -636,9 -599,7 +636,9 @@@ redef class AAttrPropde
        do
                if not has_value then return
  
 -              var mpropdef = self.mpropdef.as(not null)
 +              var mpropdef = self.mpropdef
 +              if mpropdef == null then return # skip error
 +
                var v = new TypeVisitor(modelbuilder, mpropdef.mclassdef.mmodule, mpropdef)
                self.selfvariable = v.selfvariable
  
@@@ -737,9 -698,7 +737,9 @@@ redef class AVardeclExp
  
                var decltype = mtype
                if mtype == null or mtype isa MNullType then
 -                      decltype = v.get_mclass(self, "Object").mclass_type.as_nullable
 +                      var objclass = v.get_mclass(self, "Object")
 +                      if objclass == null then return # skip error
 +                      decltype = objclass.mclass_type.as_nullable
                        if mtype == null then mtype = decltype
                end
  
  
                #debug("var {variable}: {mtype}")
  
+               self.mtype = mtype
                self.is_typed = true
        end
  end
@@@ -1043,7 -1003,7 +1044,7 @@@ redef class AForExp
                # anchor formal and virtual types
                if mtype.need_anchor then mtype = v.anchor_to(mtype)
  
 -              mtype = mtype.as_notnullable
 +              mtype = mtype.undecorate
                self.coltype = mtype.as(MClassType)
  
                # get methods is_ok, next, item
        end
  end
  
+ redef class AWithExpr
+       var method_start: nullable CallSite
+       var method_finish: nullable CallSite
+       redef fun accept_typing(v: TypeVisitor)
+       do
+               var mtype = v.visit_expr(n_expr)
+               if mtype == null then return
+               method_start = v.get_method(self, mtype, "start", n_expr isa ASelfExpr)
+               method_finish = v.get_method(self, mtype, "finish", n_expr isa ASelfExpr)
+               v.visit_stmt(n_block)
+               self.mtype = n_block.mtype
+               self.is_typed = true
+       end
+ end
  redef class AAssertExpr
        redef fun accept_typing(v)
        do
@@@ -1162,18 -1140,12 +1181,18 @@@ redef class AOrElseExp
                        return # Skip error
                end
  
 -              t1 = t1.as_notnullable
 +              if t1 isa MNullType then
 +                      v.error(n_expr, "Type error: or else on null")
 +              else if v.check_can_be_null(n_expr, t1) then
 +                      t1 = t1.as_notnull
 +              end
  
                var t = v.merge_types(self, [t1, t2])
                if t == null then
 -                      t = v.mmodule.object_type
 -                      if t2 isa MNullableType then
 +                      var c = v.get_mclass(self, "Object")
 +                      if c == null then return # forward error
 +                      t = c.mclass_type
 +                      if v.can_be_null(t2) then
                                t = t.as_nullable
                        end
                        #v.error(self, "Type Error: ambiguous type {t1} vs {t2}")
@@@ -1238,11 -1210,8 +1257,11 @@@ redef class ASuperstringExp
                var mclass = v.get_mclass(self, "String")
                if mclass == null then return # Forward error
                self.mtype = mclass.mclass_type
 +              var objclass = v.get_mclass(self, "Object")
 +              if objclass == null then return # Forward error
 +              var objtype = objclass.mclass_type
                for nexpr in self.n_exprs do
 -                      v.visit_expr_subtype(nexpr, v.mmodule.object_type)
 +                      v.visit_expr_subtype(nexpr, objtype)
                end
        end
  end
@@@ -1405,12 -1374,22 +1424,12 @@@ redef class AAsNotnullExp
                        v.error(self, "Type error: as(not null) on null")
                        return
                end
 -              if mtype isa MNullableType then
 -                      self.mtype = mtype.mtype
 -                      return
 -              end
 -              self.mtype = mtype
  
 -              if mtype isa MClassType then
 -                      v.modelbuilder.warning(self, "useless-type-test", "Warning: expression is already not null, since it is a `{mtype}`.")
 -                      return
 -              end
 -              assert mtype.need_anchor
 -              var u = v.anchor_to(mtype)
 -              if not u isa MNullableType then
 -                      v.modelbuilder.warning(self, "useless-type-test", "Warning: expression is already not null, since it is a `{mtype}: {u}`.")
 -                      return
 +              if v.check_can_be_null(n_expr, mtype) then
 +                      mtype = mtype.as_notnull
                end
 +
 +              self.mtype = mtype
        end
  end
  
@@@ -1857,8 -1836,7 +1876,8 @@@ redef class AAttrFormExp
                var mpropdefs = mproperty.lookup_definitions(v.mmodule, unsafe_type)
                assert mpropdefs.length == 1
                var mpropdef = mpropdefs.first
 -              var attr_type = mpropdef.static_mtype.as(not null)
 +              var attr_type = mpropdef.static_mtype
 +              if attr_type == null then return # skip error
                attr_type = v.resolve_for(attr_type, recvtype, self.n_expr isa ASelfExpr)
                self.attr_type = attr_type
        end