From: Jean Privat Date: Wed, 8 Apr 2015 01:01:49 +0000 (+0700) Subject: Merge: Not null types X-Git-Tag: v0.7.4~37 X-Git-Url: http://nitlanguage.org?hp=5934106e28389277fd55966cfd6064fd5ec0eebc Merge: Not null types In order to fix #86 and #1238 some preliminary work to solve remaining issues with the type system is needed. This PR is a step toward this goal. This introduce not-null types, that is a modifier to indicate that a type cannot contain null. Basically, this new modifier is almost useless because it is the semantic of all the types (except obviously null and nullable things). Except in one case: when one adapts a formal type whose bound is nullable. Before the PR, the semantic was the following ~~~nit class A[E: nullable Object] fun foo(e: E, o: nullable Object) do var o2 = o.as(not null) # the static type of o2 is `Object` print o2 # OK var e2 = e.as(not null) # the static type of e2 is still `E` because there is no `nullable` to remove print e2 # Error: expected Object, got E end end ~~~ Obviously, the issue was not that important because people managed to program complex things in Nit and I do not remember getting some complain about that particular issue. For the rare cases of this unexpected behavior, a workaround was possible: to cast on the non-nullable bound ~~~nit var e2 = e.as(Object) print e2 # OK ~~~ Nevertheless, the behavior was still buggy since type information was lost and not POLA. Moreover, `!= null` and `or else` did not have a workaround. So, this PR introduces a special new type-modifier for this case so that everything become sensible. ~~~nit var e2 = e.as(not null) # the static type of e2 is now `not null E` print e2 # OK ~~~ Moreover, a lot of local refactorisation was done in model.nit and typing.nit to clean and harmonize the code. So that independently of the new notnull modifier, the code is cleaner, some bugs where removed and some small features added, especially the detection of useless `or else`. Last, but not least, the `not null` thing is only an internal modifier and is not usable as a syntactic type construction (the grammar and the AST is unchanged); `not null` can however be shown to the programmer in messages. ~~~nit var e2 = e.as(not null) # the static type of e2 is now `not null E` var e3 = e2.as(not null) # << Warning: expression is not null, since it is a `not null E` >> ~~~ I could easily add `not null` as a specific syntactic construction since everything internally is ready. but 1. does this worth it?. 2. I do not want to conflict with #1243 that also change the grammar. As an example, is it useful to write the following? (currently refused but very easy to add after this PR) ~~~nit class A[E: nullable Object] fun foo(e: not null E): not null E do var x = e.to_s # no null pointer exception # ... return e end end var a = new A[nullable Int] var i = a.foo(5) ~~~ Pull-Request: #1244 Reviewed-by: Etienne M. Gagnon Reviewed-by: Lucas Bajolet Reviewed-by: Romain Chanoir Reviewed-by: Alexandre Terrasa --- diff --git a/contrib/benitlux/src/benitlux_daily.nit b/contrib/benitlux/src/benitlux_daily.nit index 35aa64c..bb6d1fa 100644 --- a/contrib/benitlux/src/benitlux_daily.nit +++ b/contrib/benitlux/src/benitlux_daily.nit @@ -230,11 +230,11 @@ if not opts.errors.is_empty or opts.help.value == true then end var ben = new Benitlux("sherbrooke") -ben.run(opts.send_emails.value or else false) +ben.run(opts.send_emails.value) # The parsing logic for the wellington locaiton is active (to gather data) # but the web interface do not allow to subscribe to its mailing list. # # TODO revamp mailing list Web interface ben = new Benitlux("wellington") -ben.run(opts.send_emails.value or else false) +ben.run(opts.send_emails.value) diff --git a/contrib/pep8analysis/src/flow_analysis/framework.nit b/contrib/pep8analysis/src/flow_analysis/framework.nit index e32246c..338836b 100644 --- a/contrib/pep8analysis/src/flow_analysis/framework.nit +++ b/contrib/pep8analysis/src/flow_analysis/framework.nit @@ -52,7 +52,7 @@ class FlowAnalysis[S] end if current_in != null then - in_set(block) = current_in.as(not null) + in_set(block) = current_in else continue end @@ -61,7 +61,7 @@ class FlowAnalysis[S] var old_out = out_set(block) for line in block.lines do - self.current_in = current_in.as(not null) + self.current_in = current_in self.current_out = empty_set pre_line_visit(line) enter_visit(line) diff --git a/lib/ai/search.nit b/lib/ai/search.nit index 69b5c0d..f406949 100644 --- a/lib/ai/search.nit +++ b/lib/ai/search.nit @@ -731,7 +731,7 @@ class SearchNode[S: Object, A] print "result:{state}" for n in path do var a = n.action - if a != null then print " + {a or else ""}" + if a != null then print " + {a}" print " {n.steps}: {n.state} ({n.cost}$)" end end diff --git a/lib/github/github_curl.nit b/lib/github/github_curl.nit index cc12923..8e70600 100644 --- a/lib/github/github_curl.nit +++ b/lib/github/github_curl.nit @@ -82,7 +82,7 @@ class GithubCurl if obj isa JsonObject then if obj.keys.has("message") then var title = "GithubAPIError" - var msg = obj["message"].to_s or else "" + var msg = obj["message"].to_s var err = new GithubError(msg, title) err.json["requested_uri"] = uri err.json["status_code"] = response.status_code diff --git a/lib/standard/collection/array.nit b/lib/standard/collection/array.nit index 233621e..3cc0b57 100644 --- a/lib/standard/collection/array.nit +++ b/lib/standard/collection/array.nit @@ -13,7 +13,9 @@ # This module introduces the standard array structure. # It also implements two other abstract collections : ArrayMap and ArraySet -module array +module array is + no_warning "useless-type-test" # to avoid warning with nitc while compiling with c_src +end import abstract_collection diff --git a/src/compiler/abstract_compiler.nit b/src/compiler/abstract_compiler.nit index 894a13d..3956744 100644 --- a/src/compiler/abstract_compiler.nit +++ b/src/compiler/abstract_compiler.nit @@ -2370,7 +2370,7 @@ redef class AAttrPropdef var oldnode = v.current_node v.current_node = self var old_frame = v.frame - var frame = new StaticFrame(v, self.mpropdef.as(not null), recv.mcasttype.as_notnullable.as(MClassType), [recv]) + var frame = new StaticFrame(v, self.mpropdef.as(not null), recv.mcasttype.undecorate.as(MClassType), [recv]) v.frame = frame var value diff --git a/src/compiler/separate_compiler.nit b/src/compiler/separate_compiler.nit index dab4a79..34e7be4 100644 --- a/src/compiler/separate_compiler.nit +++ b/src/compiler/separate_compiler.nit @@ -458,14 +458,14 @@ class SeparateCompiler var mtypes_by_class = new MultiHashMap[MClass, MType] for e in mtypes do - var c = e.as_notnullable.as(MClassType).mclass + var c = e.undecorate.as(MClassType).mclass mtypes_by_class[c].add(e) poset.add_node(e) end var casttypes_by_class = new MultiHashMap[MClass, MType] for e in cast_types do - var c = e.as_notnullable.as(MClassType).mclass + var c = e.undecorate.as(MClassType).mclass casttypes_by_class[c].add(e) poset.add_node(e) end @@ -510,7 +510,7 @@ class SeparateCompiler # Group cast_type by their classes var bucklets = new HashMap[MClass, Set[MType]] for e in cast_types do - var c = e.as_notnullable.as(MClassType).mclass + var c = e.undecorate.as(MClassType).mclass if not bucklets.has_key(c) then bucklets[c] = new HashSet[MType] end @@ -742,7 +742,7 @@ class SeparateCompiler # resolution table (for receiver) if is_live then - var mclass_type = mtype.as_notnullable + var mclass_type = mtype.undecorate assert mclass_type isa MClassType if resolution_tables[mclass_type].is_empty then v.add_decl("NULL, /*NO RESOLUTIONS*/") @@ -775,7 +775,7 @@ class SeparateCompiler fun compile_type_resolution_table(mtype: MType) do - var mclass_type = mtype.as_notnullable.as(MClassType) + var mclass_type = mtype.undecorate.as(MClassType) # extern const struct resolution_table_X resolution_table_X self.provide_declaration("resolution_table_{mtype.c_name}", "extern const struct types resolution_table_{mtype.c_name};") @@ -2006,7 +2006,7 @@ class SeparateCompilerVisitor fun can_be_primitive(value: RuntimeVariable): Bool do - var t = value.mcasttype.as_notnullable + var t = value.mcasttype.undecorate if not t isa MClassType then return false var k = t.mclass.kind return k == interface_kind or t.is_c_primitive diff --git a/src/highlight.nit b/src/highlight.nit index ecc4c7d..39a042a 100644 --- a/src/highlight.nit +++ b/src/highlight.nit @@ -510,6 +510,33 @@ redef class MNullableType end end +redef class MNotNullType + redef fun infobox(v) + do + return mtype.infobox(v) + end + redef fun linkto + do + var res = new HTMLTag("span") + res.append("not null ").add(mtype.linkto) + return res + end +end + +redef class MNullType + redef fun infobox(v) + do + var res = new HInfoBox(v, to_s) + return res + end + redef fun linkto + do + var res = new HTMLTag("span") + res.append("null") + return res + end +end + redef class MSignature redef fun linkto do @@ -870,8 +897,8 @@ redef class AType do var mt = mtype if mt == null then return null - mt = mt.as_notnullable - if mt isa MVirtualType or mt isa MParameterType then + mt = mt.undecorate + if mt isa MFormalType then res.add_class("nc_vt") end return mt.infobox(v) diff --git a/src/metrics/detect_covariance.nit b/src/metrics/detect_covariance.nit index dd7c36d..9090658 100644 --- a/src/metrics/detect_covariance.nit +++ b/src/metrics/detect_covariance.nit @@ -130,8 +130,8 @@ private class DetectCovariancePhase # Returns true if the test concern real generic covariance fun count_types(node, elem: ANode, sub, sup: MType, mmodule: MModule, anchor: nullable MClassType): Bool do - sub = sub.as_notnullable - sup = sup.as_notnullable + sub = sub.undecorate + sup = sup.undecorate # Category of the target type if sub isa MGenericType then @@ -254,8 +254,8 @@ private class DetectCovariancePhase fun count_cast(node: ANode, sub, sup: MType, mmodule: MModule, anchor: nullable MClassType) do var nsup = sup - sup = sup.as_notnullable - sub = sub.as_notnullable + sup = sup.undecorate + sub = sub.undecorate if sub == nsup then cpt_cast_pattern.inc("monomorphic cast!?!") @@ -445,7 +445,7 @@ redef class MType # Now the case of direct null and nullable is over. # If `sub` is a formal type, then it is accepted if its bound is accepted - while sub isa MParameterType or sub isa MVirtualType do + while sub isa MFormalType do #print "3.is {sub} a {sup}?" # A unfixed formal type can only accept itself @@ -469,7 +469,7 @@ redef class MType assert sub isa MClassType # It is the only remaining type # A unfixed formal type can only accept itself - if sup isa MParameterType or sup isa MVirtualType then + if sup isa MFormalType then return false end diff --git a/src/metrics/detect_variance_constraints.nit b/src/metrics/detect_variance_constraints.nit index 3665376..1f0205e 100644 --- a/src/metrics/detect_variance_constraints.nit +++ b/src/metrics/detect_variance_constraints.nit @@ -113,7 +113,7 @@ class DetectVarianceConstraints if pd isa MMethodDef then # Parameters (contravariant) for p in pd.msignature.mparameters do - var t = p.mtype.as_notnullable + var t = p.mtype.undecorate if not t.need_anchor then # OK else if t isa MParameterType then @@ -129,7 +129,7 @@ class DetectVarianceConstraints # Return (covariant) var t = pd.msignature.return_mtype if t != null and t.need_anchor then - t = t.as_notnullable + t = t.undecorate if t isa MParameterType then covar_pt.add(t) else if t isa MVirtualType then @@ -144,7 +144,7 @@ class DetectVarianceConstraints # Attribute (invariant) var t = pd.static_mtype if t != null and t.need_anchor then - t = t.as_notnullable + t = t.undecorate if t isa MParameterType then covar_pt.add t contravar_pt.add t @@ -161,7 +161,7 @@ class DetectVarianceConstraints # Virtual type bound (covariant) var t = pd.bound if t != null and t.need_anchor then - t = t.as_notnullable + t = t.undecorate if t isa MParameterType then covar_pt.add t else if t isa MVirtualType then @@ -223,7 +223,7 @@ class DetectVarianceConstraints # Process the generic types in a covariant position for c in covar_classes do for i in [0..c.mclass.arity[ do # The type used in the argument - var ta = c.arguments[i].as_notnullable + var ta = c.arguments[i].undecorate # The associated formal parameter var tp = c.mclass.mparameters[i] @@ -259,7 +259,7 @@ class DetectVarianceConstraints # Process the generic types in a contravariant position for c in contravar_classes do for i in [0..c.mclass.arity[ do # The type used in the argument - var ta = c.arguments[i].as_notnullable + var ta = c.arguments[i].undecorate # The associated formal parameter var tp = c.mclass.mparameters[i] diff --git a/src/metrics/rta_metrics.nit b/src/metrics/rta_metrics.nit index 1527731..00f9d02 100644 --- a/src/metrics/rta_metrics.nit +++ b/src/metrics/rta_metrics.nit @@ -374,7 +374,7 @@ redef class RapidTypeAnalysis super tnlc.values.inc(mtype) - mtype = mtype.as_notnullable + mtype = mtype.undecorate if mtype isa MClassType then cnlc.values.inc(mtype.mclass) end @@ -385,7 +385,7 @@ end redef class MType private fun signature_depth: Int do - var mtype = self.as_notnullable + var mtype = self.undecorate if not mtype isa MGenericType then return 0 var depth = 0 diff --git a/src/model/model.nit b/src/model/model.nit index 1dbedfb..62c3db4 100644 --- a/src/model/model.nit +++ b/src/model/model.nit @@ -708,6 +708,8 @@ abstract class MType if sup isa MNullableType then sup_accept_null = true sup = sup.mtype + else if sup isa MNotNullType then + sup = sup.mtype else if sup isa MNullType then sup_accept_null = true end @@ -715,16 +717,20 @@ abstract class MType # Can `sub` provide null or not? # Thus we can match with `sup_accept_null` # Also discard the nullable marker if it exists + var sub_reject_null = false if sub isa MNullableType then if not sup_accept_null then return false sub = sub.mtype + else if sub isa MNotNullType then + sub_reject_null = true + sub = sub.mtype else if sub isa MNullType then return sup_accept_null end # Now the case of direct null and nullable is over. # If `sub` is a formal type, then it is accepted if its bound is accepted - while sub isa MParameterType or sub isa MVirtualType do + while sub isa MFormalType do #print "3.is {sub} a {sup}?" # A unfixed formal type can only accept itself @@ -732,12 +738,16 @@ abstract class MType assert anchor != null sub = sub.lookup_bound(mmodule, anchor) + if sub_reject_null then sub = sub.as_notnull #print "3.is {sub} a {sup}?" # Manage the second layer of null/nullable if sub isa MNullableType then - if not sup_accept_null then return false + if not sup_accept_null and not sub_reject_null then return false + sub = sub.mtype + else if sub isa MNotNullType then + sub_reject_null = true sub = sub.mtype else if sub isa MNullType then return sup_accept_null @@ -745,10 +755,10 @@ abstract class MType end #print "4.is {sub} a {sup}? <- no more resolution" - assert sub isa MClassType # It is the only remaining type + assert sub isa MClassType else print "{sub}