From: Jean Privat Date: Fri, 16 Feb 2018 16:45:45 +0000 (-0500) Subject: Merge: Infer more attribute types X-Git-Url: http://nitlanguage.org?hp=55f6fef06bba4ab8abbd47128359d8448c61e3c0 Merge: Infer more attribute types 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 --- diff --git a/contrib/nitcc/src/grammar.nit b/contrib/nitcc/src/grammar.nit index 27a98da..feb4498 100644 --- a/contrib/nitcc/src/grammar.nit +++ b/contrib/nitcc/src/grammar.nit @@ -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] diff --git a/contrib/objcwrapper/src/objc_model.nit b/contrib/objcwrapper/src/objc_model.nit index e5c5702..76b1c2d 100644 --- a/contrib/objcwrapper/src/objc_model.nit +++ b/contrib/objcwrapper/src/objc_model.nit @@ -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 diff --git a/contrib/opportunity/src/opportunity_model.nit b/contrib/opportunity/src/opportunity_model.nit index b6f8f4b..bfd092a 100644 --- a/contrib/opportunity/src/opportunity_model.nit +++ b/contrib/opportunity/src/opportunity_model.nit @@ -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 diff --git a/contrib/pep8analysis/src/cfg/cfg_base.nit b/contrib/pep8analysis/src/cfg/cfg_base.nit index d42a4b3..6825cda 100644 --- a/contrib/pep8analysis/src/cfg/cfg_base.nit +++ b/contrib/pep8analysis/src/cfg/cfg_base.nit @@ -435,7 +435,7 @@ class BasicBlock end private class Counter - var count: Int = -1 + var count = -1 fun next : Int do count += 1 diff --git a/contrib/pep8analysis/src/model/model.nit b/contrib/pep8analysis/src/model/model.nit index beea28d..f484cc1 100644 --- a/contrib/pep8analysis/src/model/model.nit +++ b/contrib/pep8analysis/src/model/model.nit @@ -65,7 +65,7 @@ class Model end redef class ALine - var address: Int = -1 + var address = -1 fun size: Int is abstract diff --git a/contrib/sort_downloads/src/sort_downloads.nit b/contrib/sort_downloads/src/sort_downloads.nit index 9b70e83..8dc99b8 100755 --- a/contrib/sort_downloads/src/sort_downloads.nit +++ b/contrib/sort_downloads/src/sort_downloads.nit @@ -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 diff --git a/contrib/tinks/src/client/client3d.nit b/contrib/tinks/src/client/client3d.nit index 3b272d6..4f63709 100644 --- a/contrib/tinks/src/client/client3d.nit +++ b/contrib/tinks/src/client/client3d.nit @@ -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") diff --git a/contrib/tinks/src/game/framework.nit b/contrib/tinks/src/game/framework.nit index 9472da6..df30aac 100644 --- a/contrib/tinks/src/game/framework.nit +++ b/contrib/tinks/src/game/framework.nit @@ -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` # diff --git a/lib/actors/examples/chameneos-redux/chameneosredux.nit b/lib/actors/examples/chameneos-redux/chameneosredux.nit index 4b8ba9f..71e54e6 100644 --- a/lib/actors/examples/chameneos-redux/chameneosredux.nit +++ b/lib/actors/examples/chameneos-redux/chameneosredux.nit @@ -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], diff --git a/lib/bitmap/bitmap.nit b/lib/bitmap/bitmap.nit index 0aceff5..e18888c 100644 --- a/lib/bitmap/bitmap.nit +++ b/lib/bitmap/bitmap.nit @@ -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] diff --git a/lib/bucketed_game.nit b/lib/bucketed_game.nit index 28129a5..213d875 100644 --- a/lib/bucketed_game.nit +++ b/lib/bucketed_game.nit @@ -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 # diff --git a/lib/curl/curl.nit b/lib/curl/curl.nit index 0e49515..57a2423 100644 --- a/lib/curl/curl.nit +++ b/lib/curl/curl.nit @@ -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 diff --git a/lib/sax/sax_parse_exception.nit b/lib/sax/sax_parse_exception.nit index 388febc..5f780f4 100644 --- a/lib/sax/sax_parse_exception.nit +++ b/lib/sax/sax_parse_exception.nit @@ -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. # diff --git a/lib/saxophonit/lexer.nit b/lib/saxophonit/lexer.nit index 92f293b..a1f7b29 100644 --- a/lib/saxophonit/lexer.nit +++ b/lib/saxophonit/lexer.nit @@ -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 diff --git a/src/modelize/modelize_property.nit b/src/modelize/modelize_property.nit index de23d04..2ae2b59 100644 --- a/src/modelize/modelize_property.nit +++ b/src/modelize/modelize_property.nit @@ -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 index 0000000..45ff36c --- /dev/null +++ b/tests/sav/test_attr_infer_type_alt1.res @@ -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 index 0000000..9d638ad --- /dev/null +++ b/tests/test_attr_infer_type.nit @@ -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