Merge: Improve checking of virtual types
authorJean Privat <jean@pryen.org>
Wed, 8 Apr 2015 01:01:15 +0000 (08:01 +0700)
committerJean Privat <jean@pryen.org>
Wed, 8 Apr 2015 01:01:15 +0000 (08:01 +0700)
This PR does 2 things to avoid runtime errors during nitc. Invalid programs that crashed the compiler now display errors messages.

* improve the static detection of loops in virtual types.
* consider types in the signatures of properties to be fragile since they contains potentially bad virtual types.

If merged with #1241 then nitpick can process the whole tests directory without crashing (impressive, since there is 1584 border-case files).
~~~
$ ./nitpick ../tests/*.nit ../tests/alt/*.nit -I ../lib/standard/ -I ../lib/standard/collection/
[... lots of errors ...]
Errors: 1766. Warnings: 195.
~~~

Pull-Request: #1245
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Romain Chanoir <chanoir.romain@courrier.uqam.ca>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

16 files changed:
src/modelize/modelize_property.nit
tests/error_virtual_type.nit [new file with mode: 0644]
tests/error_virtual_type2.nit [new file with mode: 0644]
tests/sav/base_virtual_type7.res
tests/sav/error_virtual_type2.res [new file with mode: 0644]
tests/sav/error_virtual_type2_alt1.res [new file with mode: 0644]
tests/sav/error_virtual_type2_alt2.res [new file with mode: 0644]
tests/sav/error_virtual_type2_alt3.res [new file with mode: 0644]
tests/sav/error_virtual_type2_alt4.res [new file with mode: 0644]
tests/sav/error_virtual_type2_alt5.res [new file with mode: 0644]
tests/sav/error_virtual_type2_alt6.res [new file with mode: 0644]
tests/sav/error_virtual_type_alt1.res [new file with mode: 0644]
tests/sav/error_virtual_type_alt2.res [new file with mode: 0644]
tests/sav/error_virtual_type_alt3.res [new file with mode: 0644]
tests/sav/error_virtual_type_alt4.res [new file with mode: 0644]
tests/sav/error_virtual_type_alt5.res [new file with mode: 0644]

index 76b2d1e..cd98915 100644 (file)
@@ -103,6 +103,24 @@ redef class ModelBuilder
                                npropdef.build_signature(self)
                        end
                        for npropdef in nclassdef2.n_propdefs do
+                               if not npropdef isa ATypePropdef then continue
+                               # Check circularity
+                               var mpropdef = npropdef.mpropdef
+                               if mpropdef == null then continue
+                               if mpropdef.bound == null then continue
+                               if not check_virtual_types_circularity(npropdef, mpropdef.mproperty, mclassdef.bound_mtype, mclassdef.mmodule) then
+                                       # Invalidate the bound
+                                       mpropdef.bound = mclassdef.mmodule.model.null_type
+                               end
+                       end
+                       for npropdef in nclassdef2.n_propdefs do
+                               # Check ATypePropdef first since they may be required for the other properties
+                               if not npropdef isa ATypePropdef then continue
+                               npropdef.check_signature(self)
+                       end
+
+                       for npropdef in nclassdef2.n_propdefs do
+                               if npropdef isa ATypePropdef then continue
                                npropdef.check_signature(self)
                        end
                end
@@ -361,6 +379,8 @@ redef class ModelBuilder
                        mmodule_type = mtype.mproperty.intro_mclassdef.mmodule
                else if mtype isa MParameterType then
                        # nothing, always visible
+               else if mtype isa MNullType then
+                       # nothing to do.
                else
                        node.debug "Unexpected type {mtype}"
                        abort
@@ -389,6 +409,72 @@ redef class ModelBuilder
                        for t in mtype.arguments do check_visibility(node, t, mpropdef)
                end
        end
+
+       # Detect circularity errors for virtual types.
+       fun check_virtual_types_circularity(node: ANode, mproperty: MVirtualTypeProp, recv: MType, mmodule: MModule): Bool
+       do
+               # Check circularity
+               # Slow case: progress on each resolution until we visit all without getting a loop
+
+               # The graph used to detect loops
+               var mtype = mproperty.mvirtualtype
+               var poset = new POSet[MType]
+
+               # The work-list of types to resolve
+               var todo = new List[MType]
+               todo.add mtype
+
+               while not todo.is_empty do
+                       # The visited type
+                       var t = todo.pop
+
+                       if not t.need_anchor then continue
+
+                       # Get the types derived of `t` (subtypes and bounds)
+                       var nexts
+                       if t isa MNullableType then
+                               nexts = [t.mtype]
+                       else if t isa MGenericType then
+                               nexts = t.arguments
+                       else if t isa MVirtualType then
+                               var vt = t.mproperty
+                               # Because `vt` is possibly unchecked, we have to do the bound-lookup manually
+                               var defs = vt.lookup_definitions(mmodule, recv)
+                               # TODO something to manage correctly bound conflicts
+                               assert not defs.is_empty
+                               nexts = new Array[MType]
+                               for d in defs do
+                                       var next = defs.first.bound
+                                       if next == null then return false
+                                       nexts.add next
+                               end
+                       else if t isa MClassType then
+                               # Basic type, nothing to to
+                               continue
+                       else if t isa MParameterType then
+                               # Parameter types cannot depend on virtual types, so nothing to do
+                               continue
+                       else
+                               abort
+                       end
+
+                       # For each one
+                       for next in nexts do
+                               if poset.has_edge(next, t) then
+                                       if mtype == next then
+                                               error(node, "Error: circularity of virtual type definition: {next} <-> {t}")
+                                       else
+                                               error(node, "Error: circularity of virtual type definition: {mtype} -> {next} <-> {t}")
+                                       end
+                                       return false
+                               else
+                                       poset.add_edge(t, next)
+                                       todo.add next
+                               end
+                       end
+               end
+               return true
+       end
 end
 
 redef class MPropDef
@@ -599,7 +685,7 @@ redef class ASignature
                        param_names.add(np.n_id.text)
                        var ntype = np.n_type
                        if ntype != null then
-                               var mtype = modelbuilder.resolve_mtype(mmodule, mclassdef, ntype)
+                               var mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
                                if mtype == null then return false # Skip error
                                for i in [0..param_names.length-param_types.length[ do
                                        param_types.add(mtype)
@@ -616,7 +702,7 @@ redef class ASignature
                end
                var ntype = self.n_type
                if ntype != null then
-                       self.ret_type = modelbuilder.resolve_mtype(mmodule, mclassdef, ntype)
+                       self.ret_type = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
                        if self.ret_type == null then return false # Skip error
                end
 
@@ -624,24 +710,24 @@ redef class ASignature
                return true
        end
 
-       # Build a visited signature
-       fun build_signature(modelbuilder: ModelBuilder): nullable MSignature
+       private fun check_signature(modelbuilder: ModelBuilder, mclassdef: MClassDef): Bool
        do
-               if param_names.length != param_types.length then
-                       # Some parameters are typed, other parameters are not typed.
-                       modelbuilder.error(self.n_params[param_types.length], "Error: Untyped parameter `{param_names[param_types.length]}'.")
-                       return null
+               var res = true
+               for np in self.n_params do
+                       var ntype = np.n_type
+                       if ntype != null then
+                               if modelbuilder.resolve_mtype(mclassdef.mmodule, mclassdef, ntype) == null then
+                                       res = false
+                               end
+                       end
                end
-
-               var mparameters = new Array[MParameter]
-               for i in [0..param_names.length[ do
-                       var mparameter = new MParameter(param_names[i], param_types[i], i == vararg_rank)
-                       self.n_params[i].mparameter = mparameter
-                       mparameters.add(mparameter)
+               var ntype = self.n_type
+               if ntype != null then
+                       if modelbuilder.resolve_mtype(mclassdef.mmodule, mclassdef, ntype) == null then
+                               res = false
+                       end
                end
-
-               var msignature = new MSignature(mparameters, ret_type)
-               return msignature
+               return res
        end
 end
 
@@ -882,6 +968,14 @@ redef class AMethPropdef
                var mysignature = self.mpropdef.msignature
                if mysignature == null then return # Error thus skiped
 
+               # Check
+               if nsig != null then
+                       if not nsig.check_signature(modelbuilder, mclassdef) then
+                               self.mpropdef.msignature = null # invalidate
+                               return # Forward error
+                       end
+               end
+
                # Lookup for signature in the precursor
                # FIXME all precursors should be considered
                if not mpropdef.is_intro then
@@ -1098,7 +1192,7 @@ redef class AAttrPropdef
 
                var ntype = self.n_type
                if ntype != null then
-                       mtype = modelbuilder.resolve_mtype(mmodule, mclassdef, ntype)
+                       mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
                        if mtype == null then return
                end
 
@@ -1119,7 +1213,7 @@ redef class AAttrPropdef
                if mtype == null then
                        if nexpr != null then
                                if nexpr isa ANewExpr then
-                                       mtype = modelbuilder.resolve_mtype(mmodule, mclassdef, nexpr.n_type)
+                                       mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, nexpr.n_type, true)
                                else if nexpr isa AIntExpr then
                                        var cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int")
                                        if cla != null then mtype = cla.mclass_type
@@ -1146,7 +1240,7 @@ redef class AAttrPropdef
                        end
                else if ntype != null and inherited_type == mtype then
                        if nexpr isa ANewExpr then
-                               var xmtype = modelbuilder.resolve_mtype(mmodule, mclassdef, nexpr.n_type)
+                               var xmtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, nexpr.n_type, true)
                                if xmtype == mtype then
                                        modelbuilder.advice(ntype, "useless-type", "Warning: useless type definition")
                                end
@@ -1190,6 +1284,18 @@ redef class AAttrPropdef
                var mtype = self.mpropdef.static_mtype
                if mtype == null then return # Error thus skipped
 
+               var mclassdef = mpropdef.mclassdef
+               var mmodule = mclassdef.mmodule
+
+               # Check types
+               if ntype != null then
+                       if modelbuilder.resolve_mtype(mmodule, mclassdef, ntype) == null then return
+               end
+               var nexpr = n_expr
+               if nexpr isa ANewExpr then
+                       if modelbuilder.resolve_mtype(mmodule, mclassdef, nexpr.n_type) == null then return
+               end
+
                # Lookup for signature in the precursor
                # FIXME all precursors should be considered
                if not mpropdef.is_intro then
@@ -1321,7 +1427,7 @@ redef class ATypePropdef
                var mtype: nullable MType = null
 
                var ntype = self.n_type
-               mtype = modelbuilder.resolve_mtype(mmodule, mclassdef, ntype)
+               mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
                if mtype == null then return
 
                mpropdef.bound = mtype
@@ -1333,7 +1439,7 @@ redef class ATypePropdef
                var mpropdef = self.mpropdef
                if mpropdef == null then return # Error thus skipped
 
-               var bound = self.mpropdef.bound
+               var bound = mpropdef.bound
                if bound == null then return # Error thus skipped
 
                modelbuilder.check_visibility(n_type, bound, mpropdef)
@@ -1342,25 +1448,13 @@ redef class ATypePropdef
                var mmodule = mclassdef.mmodule
                var anchor = mclassdef.bound_mtype
 
-               # Check circularity
-               if bound isa MVirtualType then
-                       # Slow case: progress on each resolution until: (i) we loop, or (ii) we found a non formal type
-                       var seen = [self.mpropdef.mproperty.mvirtualtype]
-                       loop
-                               if seen.has(bound) then
-                                       seen.add(bound)
-                                       modelbuilder.error(self, "Error: circularity of virtual type definition: {seen.join(" -> ")}")
-                                       return
-                               end
-                               seen.add(bound)
-                               var next = bound.lookup_bound(mmodule, anchor)
-                               if not next isa MVirtualType then break
-                               bound = next
-                       end
+               var ntype = self.n_type
+               if modelbuilder.resolve_mtype(mmodule, mclassdef, ntype) == null then
+                       mpropdef.bound = null
+                       return
                end
 
                # Check redefinitions
-               bound = mpropdef.bound.as(not null)
                for p in mpropdef.mproperty.lookup_super_definitions(mmodule, anchor) do
                        var supbound = p.bound
                        if supbound == null then break # broken super bound, skip error
diff --git a/tests/error_virtual_type.nit b/tests/error_virtual_type.nit
new file mode 100644 (file)
index 0000000..dd7243b
--- /dev/null
@@ -0,0 +1,35 @@
+# 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 standard::kernel
+
+class G[E: Object]
+end
+
+class A
+       fun foo(t: T): Comparable do return t
+       type T: Comparable #alt1-5#
+       #alt1#type T: T
+       #alt2#type T: nullable T
+       #alt3#type T: G[T]
+       #alt4#type T: U
+       #alt4#type U: FAIL
+       #alt5#type T: U
+       #alt5#type U: T
+       fun bar(t: T): Comparable do return t
+end
+
+var a = new A
+a.foo(1)
+a.bar('1')
diff --git a/tests/error_virtual_type2.nit b/tests/error_virtual_type2.nit
new file mode 100644 (file)
index 0000000..274bb77
--- /dev/null
@@ -0,0 +1,52 @@
+# 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 standard::kernel
+
+class G[E]
+end
+
+class A
+       fun foo(t: T): Comparable do return t
+       #fun foog: nullable G[T] do return barg
+       type T: Comparable #alt1-5#
+       type GT: G[T]
+       #alt1#type T: T
+       #alt2#type T: nullable T
+       #alt3#type T: G[T]
+       #alt4#type T: U
+       #alt4#type U: FAIL
+       #alt5#type T: U
+       #alt5#type U: T
+       fun bar(t: T): Comparable do return t
+       #fun barg: nullable G[T] do return foog
+end
+
+class B
+       super A
+       redef fun foo(t: T): T do return t
+       #redef fun foog: GT do return barg
+       redef type GT: G[Discrete]
+       #alt6#redef type GT: G[Bool]
+       redef fun bar(t: T): T do return t
+       #redef fun barg: GT do return foog
+end
+
+var a = new A
+a.foo(1)
+a.bar('1')
+
+var b = new B
+b.foo(2)
+b.bar('3')
index 0fc0245..01f9a2f 100644 (file)
@@ -1,2 +1 @@
-base_virtual_type7.nit:20,2--10: Error: circularity of virtual type definition: E -> F -> E
-base_virtual_type7.nit:21,2--10: Error: circularity of virtual type definition: F -> E -> F
+base_virtual_type7.nit:20,2--10: Error: circularity of virtual type definition: E <-> F
diff --git a/tests/sav/error_virtual_type2.res b/tests/sav/error_virtual_type2.res
new file mode 100644 (file)
index 0000000..417f403
--- /dev/null
@@ -0,0 +1 @@
+error_virtual_type2.nit:40,17--26: Redef Error: Wrong bound type. Found G[Discrete], expected a subtype of G[T], as in error_virtual_type2#A#GT.
diff --git a/tests/sav/error_virtual_type2_alt1.res b/tests/sav/error_virtual_type2_alt1.res
new file mode 100644 (file)
index 0000000..38cf3dd
--- /dev/null
@@ -0,0 +1,5 @@
+alt/error_virtual_type2_alt1.nit:22,2--24,13: Error: circularity of virtual type definition: GT -> T <-> T
+alt/error_virtual_type2_alt1.nit:25,2--10: Error: circularity of virtual type definition: T <-> T
+alt/error_virtual_type2_alt1.nit:38,23: Redef Error: Wrong return type. found T, expected Comparable as in error_virtual_type2_alt1#A#foo.
+alt/error_virtual_type2_alt1.nit:40,17--26: Redef Error: Wrong bound type. Found G[Discrete], expected a subtype of null, as in error_virtual_type2_alt1#A#GT.
+alt/error_virtual_type2_alt1.nit:42,23: Redef Error: Wrong return type. found T, expected Comparable as in error_virtual_type2_alt1#A#bar.
diff --git a/tests/sav/error_virtual_type2_alt2.res b/tests/sav/error_virtual_type2_alt2.res
new file mode 100644 (file)
index 0000000..f155524
--- /dev/null
@@ -0,0 +1,5 @@
+alt/error_virtual_type2_alt2.nit:22,2--24,13: Error: circularity of virtual type definition: GT -> T <-> nullable T
+alt/error_virtual_type2_alt2.nit:25,2--26,19: Error: circularity of virtual type definition: T <-> nullable T
+alt/error_virtual_type2_alt2.nit:38,23: Redef Error: Wrong return type. found T, expected Comparable as in error_virtual_type2_alt2#A#foo.
+alt/error_virtual_type2_alt2.nit:40,17--26: Redef Error: Wrong bound type. Found G[Discrete], expected a subtype of null, as in error_virtual_type2_alt2#A#GT.
+alt/error_virtual_type2_alt2.nit:42,23: Redef Error: Wrong return type. found T, expected Comparable as in error_virtual_type2_alt2#A#bar.
diff --git a/tests/sav/error_virtual_type2_alt3.res b/tests/sav/error_virtual_type2_alt3.res
new file mode 100644 (file)
index 0000000..80c9981
--- /dev/null
@@ -0,0 +1,5 @@
+alt/error_virtual_type2_alt3.nit:22,2--24,13: Error: circularity of virtual type definition: GT -> G[T] <-> T
+alt/error_virtual_type2_alt3.nit:25,2--27,12: Error: circularity of virtual type definition: T <-> G[T]
+alt/error_virtual_type2_alt3.nit:38,23: Redef Error: Wrong return type. found T, expected Comparable as in error_virtual_type2_alt3#A#foo.
+alt/error_virtual_type2_alt3.nit:40,17--26: Redef Error: Wrong bound type. Found G[Discrete], expected a subtype of null, as in error_virtual_type2_alt3#A#GT.
+alt/error_virtual_type2_alt3.nit:42,23: Redef Error: Wrong return type. found T, expected Comparable as in error_virtual_type2_alt3#A#bar.
diff --git a/tests/sav/error_virtual_type2_alt4.res b/tests/sav/error_virtual_type2_alt4.res
new file mode 100644 (file)
index 0000000..158622e
--- /dev/null
@@ -0,0 +1,4 @@
+alt/error_virtual_type2_alt4.nit:29,10--13: Type error: class FAIL not found in module error_virtual_type2_alt4.
+alt/error_virtual_type2_alt4.nit:38,23: Redef Error: Wrong return type. found T, expected Comparable as in error_virtual_type2_alt4#A#foo.
+alt/error_virtual_type2_alt4.nit:40,17--26: Redef Error: Wrong bound type. Found G[Discrete], expected a subtype of null, as in error_virtual_type2_alt4#A#GT.
+alt/error_virtual_type2_alt4.nit:42,23: Redef Error: Wrong return type. found T, expected Comparable as in error_virtual_type2_alt4#A#bar.
diff --git a/tests/sav/error_virtual_type2_alt5.res b/tests/sav/error_virtual_type2_alt5.res
new file mode 100644 (file)
index 0000000..1efe3e7
--- /dev/null
@@ -0,0 +1,5 @@
+alt/error_virtual_type2_alt5.nit:22,2--24,13: Error: circularity of virtual type definition: GT -> T <-> U
+alt/error_virtual_type2_alt5.nit:25,2--30,10: Error: circularity of virtual type definition: T <-> U
+alt/error_virtual_type2_alt5.nit:38,23: Redef Error: Wrong return type. found T, expected Comparable as in error_virtual_type2_alt5#A#foo.
+alt/error_virtual_type2_alt5.nit:40,17--26: Redef Error: Wrong bound type. Found G[Discrete], expected a subtype of null, as in error_virtual_type2_alt5#A#GT.
+alt/error_virtual_type2_alt5.nit:42,23: Redef Error: Wrong return type. found T, expected Comparable as in error_virtual_type2_alt5#A#bar.
diff --git a/tests/sav/error_virtual_type2_alt6.res b/tests/sav/error_virtual_type2_alt6.res
new file mode 100644 (file)
index 0000000..6e05965
--- /dev/null
@@ -0,0 +1,2 @@
+alt/error_virtual_type2_alt6.nit:40,17--26: Redef Error: Wrong bound type. Found G[Discrete], expected a subtype of G[T], as in error_virtual_type2_alt6#A#GT.
+alt/error_virtual_type2_alt6.nit:41,2--22: Error: A property GT is already defined in class B at line 39.
diff --git a/tests/sav/error_virtual_type_alt1.res b/tests/sav/error_virtual_type_alt1.res
new file mode 100644 (file)
index 0000000..b87d6c0
--- /dev/null
@@ -0,0 +1 @@
+alt/error_virtual_type_alt1.nit:22,2--23,10: Error: circularity of virtual type definition: T <-> T
diff --git a/tests/sav/error_virtual_type_alt2.res b/tests/sav/error_virtual_type_alt2.res
new file mode 100644 (file)
index 0000000..98ff344
--- /dev/null
@@ -0,0 +1 @@
+alt/error_virtual_type_alt2.nit:22,2--24,19: Error: circularity of virtual type definition: T <-> nullable T
diff --git a/tests/sav/error_virtual_type_alt3.res b/tests/sav/error_virtual_type_alt3.res
new file mode 100644 (file)
index 0000000..1bad756
--- /dev/null
@@ -0,0 +1,2 @@
+alt/error_virtual_type_alt3.nit:25,12: Type error: expected Object, got T
+alt/error_virtual_type_alt3.nit:22,2--25,12: Error: circularity of virtual type definition: T <-> G[T]
diff --git a/tests/sav/error_virtual_type_alt4.res b/tests/sav/error_virtual_type_alt4.res
new file mode 100644 (file)
index 0000000..150aef7
--- /dev/null
@@ -0,0 +1 @@
+alt/error_virtual_type_alt4.nit:27,10--13: Type error: class FAIL not found in module error_virtual_type_alt4.
diff --git a/tests/sav/error_virtual_type_alt5.res b/tests/sav/error_virtual_type_alt5.res
new file mode 100644 (file)
index 0000000..84b89c9
--- /dev/null
@@ -0,0 +1 @@
+alt/error_virtual_type_alt5.nit:22,2--28,10: Error: circularity of virtual type definition: T <-> U