Skip to content

Commit

Permalink
Merge: Infer more attribute types
Browse files Browse the repository at this point in the history
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
  • Loading branch information
privat committed Feb 16, 2018
2 parents 55f6fef + 72741c1 commit b4e876e
Show file tree
Hide file tree
Showing 17 changed files with 156 additions and 83 deletions.
2 changes: 1 addition & 1 deletion contrib/nitcc/src/grammar.nit
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion contrib/objcwrapper/src/objc_model.nit
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions contrib/opportunity/src/opportunity_model.nit
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion contrib/pep8analysis/src/cfg/cfg_base.nit
Expand Up @@ -435,7 +435,7 @@ class BasicBlock
end

private class Counter
var count: Int = -1
var count = -1
fun next : Int
do
count += 1
Expand Down
2 changes: 1 addition & 1 deletion contrib/pep8analysis/src/model/model.nit
Expand Up @@ -65,7 +65,7 @@ class Model
end

redef class ALine
var address: Int = -1
var address = -1

fun size: Int is abstract

Expand Down
2 changes: 1 addition & 1 deletion contrib/sort_downloads/src/sort_downloads.nit
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions contrib/tinks/src/client/client3d.nit
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion contrib/tinks/src/game/framework.nit
Expand Up @@ -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`
#
Expand Down
4 changes: 2 additions & 2 deletions lib/actors/examples/chameneos-redux/chameneosredux.nit
Expand Up @@ -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],
Expand Down
4 changes: 2 additions & 2 deletions lib/bitmap/bitmap.nit
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion lib/bucketed_game.nit
Expand Up @@ -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
#
Expand Down
2 changes: 1 addition & 1 deletion lib/curl/curl.nit
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions lib/sax/sax_parse_exception.nit
Expand Up @@ -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.
#
Expand Down
2 changes: 1 addition & 1 deletion lib/saxophonit/lexer.nit
Expand Up @@ -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
Expand Down
159 changes: 99 additions & 60 deletions src/modelize/modelize_property.nit
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions 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
30 changes: 30 additions & 0 deletions 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

0 comments on commit b4e876e

Please sign in to comment.