Merge: Infer more attribute types
authorJean Privat <jean@pryen.org>
Fri, 16 Feb 2018 16:45:45 +0000 (11:45 -0500)
committerJean Privat <jean@pryen.org>
Fri, 16 Feb 2018 16:45:45 +0000 (11:45 -0500)
Extend the detection of the static type of attributes from their literal values to support three new cases:

* Simple arrays like `[0, 1, 2]` and `[new Set[Int], new Set[Int]]`. However, it does not accept arrays with an explicit type because we can't subtype/anchor at that point, as far as I know.

* Negative integers and floats. This cheats a bit as the return type of the unary - is defined in the core libary. However this should help 99.9% of the time, in particular for Nit beginners, and a workaround is to declare the attribute static type when defining a different kernel library.

* The `once` keyword.

~~~
class A
       # Now detected
       var i = -1
       var f = -1.0
       var a = [0, 1]
       var o = once [0, 1]

       # These are refused
       var a1 = [0, 1.0, "a"] # Different types
       var a2 = [0, 1: Int] # Can't reliably check subtypes
       var a4 = [1+1] # Expression
       var o1 = once [0, "a"] # Forwarded error
end
~~~

---

You may want to review commit by commit as the first commit is a small refactoring.

Pull-Request: #2614

17 files changed:
contrib/nitcc/src/grammar.nit
contrib/objcwrapper/src/objc_model.nit
contrib/opportunity/src/opportunity_model.nit
contrib/pep8analysis/src/cfg/cfg_base.nit
contrib/pep8analysis/src/model/model.nit
contrib/sort_downloads/src/sort_downloads.nit
contrib/tinks/src/client/client3d.nit
contrib/tinks/src/game/framework.nit
lib/actors/examples/chameneos-redux/chameneosredux.nit
lib/bitmap/bitmap.nit
lib/bucketed_game.nit
lib/curl/curl.nit
lib/sax/sax_parse_exception.nit
lib/saxophonit/lexer.nit
src/modelize/modelize_property.nit
tests/sav/test_attr_infer_type_alt1.res [new file with mode: 0644]
tests/test_attr_infer_type.nit [new file with mode: 0644]

index 27a98da..feb4498 100644 (file)
@@ -897,7 +897,7 @@ class LRState
        var cname: String is lazy do return name.to_cmangle
 
        # Number
-       var number: Int = -1
+       var number = -1
 
        # Set of all items
        var items = new HashSet[Item]
index e5c5702..76b1c2d 100644 (file)
@@ -45,7 +45,7 @@ class ObjcModel
        # Objective-C types available in imported modules
        #
        # TODO seach in existing wrappers
-       private var imported_types: Array[String] = ["NSObject", "NSString"]
+       private var imported_types = ["NSObject", "NSString"]
 end
 
 # Objective-C class
index b6f8f4b..bfd092a 100644 (file)
@@ -127,7 +127,7 @@ class People
        super DBObject
 
        # ID in the Database, -1 if not set
-       var id: Int = -1
+       var id = -1
        # Name of the participant
        var name: String
        # Surname of the participant
@@ -270,7 +270,7 @@ class Answer
        # Name of the answer (title)
        var name: String
        # Id in the database, -1 if not set
-       var id: Int = -1
+       var id = -1
        # Meetup the answer is linked to (null while it is not added in the database or set via API)
        var meetup: nullable Meetup = null is writable
 
index d42a4b3..6825cda 100644 (file)
@@ -435,7 +435,7 @@ class BasicBlock
 end
 
 private class Counter
-       var count: Int = -1
+       var count = -1
        fun next : Int
        do
                count += 1
index beea28d..f484cc1 100644 (file)
@@ -65,7 +65,7 @@ class Model
 end
 
 redef class ALine
-       var address: Int = -1
+       var address = -1
 
        fun size: Int is abstract
 
index 9b70e83..8dc99b8 100755 (executable)
@@ -47,7 +47,7 @@ class Config
 
        # Super directories with wanted folder names, which will be used to sort
        # the files (only their name are used, the files won't be copied there).
-       var regex_source_dirs: Array[String] = ["~/Videos/"]
+       var regex_source_dirs = ["~/Videos/"]
 
        # Will only sort files older than the number of `elapsed_days`.
        var elapsed_days = 7
index 3b272d6..4f63709 100644 (file)
@@ -41,23 +41,23 @@ redef class App
        # Models
 
        # Models of rocks
-       var models_rock = new Array[Model].with_items(
+       var models_rock = [
                new Model("models/Tall_Rock_1_01.obj"),
                new Model("models/Tall_Rock_2_01.obj"),
                new Model("models/Tall_Rock_3_01.obj"),
-               new Model("models/Tall_Rock_4_01.obj"))
+               new Model("models/Tall_Rock_4_01.obj")]
 
        # Models of trees
-       var models_tree = new Array[Model].with_items(
+       var models_tree = [
                new Model("models/Oak_Dark_01.obj"),
                new Model("models/Oak_Green_01.obj"),
                new Model("models/Large_Oak_Dark_01.obj"),
-               new Model("models/Large_Oak_Green_01.obj"))
+               new Model("models/Large_Oak_Green_01.obj")]
 
        # Models of the debris left by a destroyed tank
-       var models_debris = new Array[Model].with_items(
+       var models_debris = [
                new Model("models/debris0.obj"),
-               new Model("models/debris1.obj"))
+               new Model("models/debris1.obj")]
 
        # Model the health pickup
        var model_health = new Model("models/health.obj")
index 9472da6..df30aac 100644 (file)
@@ -31,7 +31,7 @@ class TGame
        private var clock = new Clock is noserialize
 
        # Tick count of the last turn (The first turn as a tick of 0)
-       var tick: Int = -1
+       var tick = -1
 
        # Execute the next turn and return it as a `TTurn`
        #
index 4b8ba9f..71e54e6 100644 (file)
@@ -83,8 +83,8 @@ redef class Sys
        fun blue: Int do return 0
        fun red: Int do return 1
        fun yellow: Int do return 2
-       var numbers: Array[String] = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
-       var colors: Array[String] = ["blue", "red", "yellow"]
+       var numbers = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
+       var colors = ["blue", "red", "yellow"]
        # Matrix for complementing colors
        var complements: Array[Array[Int]] = [[0, 2, 1],
                                              [2, 1, 0],
index 0aceff5..e18888c 100644 (file)
@@ -74,10 +74,10 @@ class Bitmap
        private var image_size: Int is noinit
 
        # 14-byte bitmap header
-       private var bitmap_header: Array[Int] = [66, 77, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0]
+       private var bitmap_header = [66, 77, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0]
 
        # 40-byte dib header
-       private var dib_header: Array[Int] = [40, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       private var dib_header = [40, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 1, 0, 24, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
index 28129a5..213d875 100644 (file)
@@ -56,7 +56,7 @@ class Buckets[G: Game]
        type BUCKET: HashSet[Bucketable[G]]
 
        private var next_bucket: nullable BUCKET = null
-       private var current_bucket_key: Int = -1
+       private var current_bucket_key = -1
 
        # Number of `buckets`, default at 100
        #
index 0e49515..57a2423 100644 (file)
@@ -293,7 +293,7 @@ class CurlMail
        # Protocols supported to send mail to a server
        #
        # Default value at `["smtp", "smtps"]`
-       var supported_outgoing_protocol: Array[String] = ["smtp", "smtps"]
+       var supported_outgoing_protocol = ["smtp", "smtps"]
 
        # Helper method to add pair values to mail content while building it (ex: "To:", "address@mail.com")
        private fun add_pair_to_content(str: String, att: String, val: nullable String): String
index 388febc..5f780f4 100644 (file)
@@ -45,11 +45,11 @@ class SAXParseException
 
        # The line number of the end of the text that
        # caused the error or warning, or -1.
-       var line_number: Int = -1
+       var line_number = -1
 
        # The column number of the end of the text that
        # caused the error or warning, or -1.
-       var column_number: Int = -1
+       var column_number = -1
 
        # Create a new SAXParseException from a message and a Locator.
        #
index 92f293b..a1f7b29 100644 (file)
@@ -37,7 +37,7 @@ class XophonLexer
        # Last read byte.
        #
        # Equals `-1` on end of file or error.
-       private var last_char: Int = -1
+       private var last_char = -1
 
        # Before end-of-line handling, was the last read byte a CARRIAGE RETURN?
        private var was_cr: Bool = false
index de23d04..2ae2b59 100644 (file)
@@ -1378,66 +1378,7 @@ redef class AAttrPropdef
                var nexpr = self.n_expr
                if mtype == null then
                        if nexpr != null then
-                               if nexpr isa ANewExpr then
-                                       mtype = modelbuilder.resolve_mtype_unchecked(mclassdef, nexpr.n_type, true)
-                               else if nexpr isa AAsCastExpr then
-                                       mtype = modelbuilder.resolve_mtype_unchecked(mclassdef, nexpr.n_type, true)
-                               else if nexpr isa AIntegerExpr then
-                                       var cla: nullable MClass = null
-                                       if nexpr.value isa Int then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int")
-                                       else if nexpr.value isa Byte then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Byte")
-                                       else if nexpr.value isa Int8 then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int8")
-                                       else if nexpr.value isa Int16 then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int16")
-                                       else if nexpr.value isa UInt16 then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "UInt16")
-                                       else if nexpr.value isa Int32 then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int32")
-                                       else if nexpr.value isa UInt32 then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "UInt32")
-                                       else
-                                               # Should not happen, and should be updated as new types are added
-                                               abort
-                                       end
-                                       if cla != null then mtype = cla.mclass_type
-                               else if nexpr isa AFloatExpr then
-                                       var cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Float")
-                                       if cla != null then mtype = cla.mclass_type
-                               else if nexpr isa ACharExpr then
-                                       var cla: nullable MClass
-                                       if nexpr.is_ascii then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Byte")
-                                       else if nexpr.is_code_point then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int")
-                                       else
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Char")
-                                       end
-                                       if cla != null then mtype = cla.mclass_type
-                               else if nexpr isa ABoolExpr then
-                                       var cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Bool")
-                                       if cla != null then mtype = cla.mclass_type
-                               else if nexpr isa ASuperstringExpr then
-                                       var cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "String")
-                                       if cla != null then mtype = cla.mclass_type
-                               else if nexpr isa AStringFormExpr then
-                                       var cla: nullable MClass
-                                       if nexpr.is_bytestring then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Bytes")
-                                       else if nexpr.is_re then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Regex")
-                                       else if nexpr.is_string then
-                                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "String")
-                                       else
-                                               abort
-                                       end
-                                       if cla != null then mtype = cla.mclass_type
-                               else
-                                       modelbuilder.error(self, "Error: untyped attribute `{mreadpropdef}`. Implicit typing allowed only for literals and new.")
-                               end
-
+                               mtype = infer_static_type(modelbuilder, nexpr, mclassdef, mmodule, mreadpropdef)
                                if mtype == null then return
                        end
                else if ntype != null and inherited_type == mtype then
@@ -1485,6 +1426,104 @@ redef class AAttrPropdef
                check_repeated_types(modelbuilder)
        end
 
+       # Detect the static type from the value assigned to the attribute `self`
+       #
+       # Return the static type if it can be safely inferred.
+       private fun infer_static_type(modelbuilder: ModelBuilder, nexpr: AExpr,
+               mclassdef: MClassDef, mmodule: MModule, mreadpropdef: MPropDef): nullable MType
+       do
+               var mtype = null
+               if nexpr isa ANewExpr then
+                       mtype = modelbuilder.resolve_mtype_unchecked(mclassdef, nexpr.n_type, true)
+               else if nexpr isa AAsCastExpr then
+                       mtype = modelbuilder.resolve_mtype_unchecked(mclassdef, nexpr.n_type, true)
+               else if nexpr isa AIntegerExpr then
+                       var cla: nullable MClass = null
+                       if nexpr.value isa Int then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int")
+                       else if nexpr.value isa Byte then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Byte")
+                       else if nexpr.value isa Int8 then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int8")
+                       else if nexpr.value isa Int16 then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int16")
+                       else if nexpr.value isa UInt16 then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "UInt16")
+                       else if nexpr.value isa Int32 then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int32")
+                       else if nexpr.value isa UInt32 then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "UInt32")
+                       else
+                               # Should not happen, and should be updated as new types are added
+                               abort
+                       end
+                       if cla != null then mtype = cla.mclass_type
+               else if nexpr isa AFloatExpr then
+                       var cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Float")
+                       if cla != null then mtype = cla.mclass_type
+               else if nexpr isa ACharExpr then
+                       var cla: nullable MClass
+                       if nexpr.is_ascii then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Byte")
+                       else if nexpr.is_code_point then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int")
+                       else
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Char")
+                       end
+                       if cla != null then mtype = cla.mclass_type
+               else if nexpr isa ABoolExpr then
+                       var cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Bool")
+                       if cla != null then mtype = cla.mclass_type
+               else if nexpr isa ASuperstringExpr then
+                       var cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "String")
+                       if cla != null then mtype = cla.mclass_type
+               else if nexpr isa AStringFormExpr then
+                       var cla: nullable MClass
+                       if nexpr.is_bytestring then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Bytes")
+                       else if nexpr.is_re then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Regex")
+                       else if nexpr.is_string then
+                               cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "String")
+                       else
+                               abort
+                       end
+                       if cla != null then mtype = cla.mclass_type
+               else if nexpr isa AArrayExpr and nexpr.n_type == null and nexpr.n_exprs.not_empty then
+                       # Non-empty arrays without an explicit type
+
+                       var item_mtypes = new Set[MType]
+                       var fails = false
+                       for node in nexpr.n_exprs do
+                               var item_mtype = infer_static_type(modelbuilder, node, mclassdef, mmodule, mreadpropdef)
+                               if item_mtype == null then
+                                       fails = true
+                               else
+                                       item_mtypes.add item_mtype
+                               end
+                       end
+
+                       if fails then return null # Failed to infer some types
+
+                       if item_mtypes.length > 1 then
+                               modelbuilder.error(self, "Type Error: ambiguous array type {item_mtypes.join(" ")}")
+                       end
+
+                       mtype = mmodule.array_type(item_mtypes.first)
+               else if nexpr isa AUminusExpr and (nexpr.n_expr isa AIntegerExpr or nexpr.n_expr isa AFloatExpr) then
+                       # The Int and Float unary - is defined in `kernel`, so this may
+                       # result in an invalid behavior when using a custom kernel.
+                       # A workaround is to declare the attribute static type.
+                       # This is still very useful, especially to novice programmers.
+                       mtype = infer_static_type(modelbuilder, nexpr.n_expr, mclassdef, mmodule, mreadpropdef)
+               else if nexpr isa AOnceExpr then
+                       mtype = infer_static_type(modelbuilder, nexpr.n_expr, mclassdef, mmodule, mreadpropdef)
+               else
+                       modelbuilder.error(self, "Error: untyped attribute `{mreadpropdef}`. Implicit typing allowed only for literals and new.")
+               end
+               return mtype
+       end
+
        redef fun check_signature(modelbuilder)
        do
                var mpropdef = self.mpropdef
diff --git a/tests/sav/test_attr_infer_type_alt1.res b/tests/sav/test_attr_infer_type_alt1.res
new file mode 100644 (file)
index 0000000..45ff36c
--- /dev/null
@@ -0,0 +1,4 @@
+alt/test_attr_infer_type_alt1.nit:23,5--6: Type Error: ambiguous array type Int Float String
+alt/test_attr_infer_type_alt1.nit:24,5--6: Error: untyped attribute `test_attr_infer_type_alt1$A$a2`. Implicit typing allowed only for literals and new.
+alt/test_attr_infer_type_alt1.nit:25,5--6: Error: untyped attribute `test_attr_infer_type_alt1$A$a4`. Implicit typing allowed only for literals and new.
+alt/test_attr_infer_type_alt1.nit:27,5--6: Type Error: ambiguous array type Int String
diff --git a/tests/test_attr_infer_type.nit b/tests/test_attr_infer_type.nit
new file mode 100644 (file)
index 0000000..9d638ad
--- /dev/null
@@ -0,0 +1,30 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import collection
+import text
+
+class A
+       var i0 = 0
+       var i1 = -1
+       var f = -1.0
+       var a0 = [0, 1]
+#alt1# var a1 = [0, 1.0, "a"] # Different types
+#alt1# var a2 = [0, 1: Int] # Can't reliably check subtypes
+#alt1# var a4 = [1+1] # Expression
+       var o0 = once [0, 2]
+#alt1# var o1 = once [0, "a"] # Forwarded error
+end
+
+var a = new A