Property definitions

nitc $ DetectVarianceConstraints :: defaultinit
# A specific analysis that detects the variance constraints of formal parameters.
#
# The client has 3 steps to do:
#
#  * call `collect` to initialize the attributes.
#  * call `propagate` to propagate the variance constraints.
#  * call `print_stats` to print the results.
class DetectVarianceConstraints
	# Collect all types used in covariant and contravariant positions.
	#
	# The collect visits all classes and properties of `mainmodule` and its imported modules.
	#
	# After the visit, the attributes of `self` are filled.
	fun collect(mainmodule: MModule)
	do
		for m in mainmodule.in_importation.greaters do
			for cd in m.mclassdefs do
				if cd.is_intro then
					pts.add_all(cd.mclass.mparameters)
					var a = cd.mclass.arity
					if a == 0 then
						cpt_class.inc("non generic")
					else if a == 1 then
						cpt_class.inc("with 1 formal type parameter")
					else
						cpt_class.inc("with {a} formal type parameters")
					end
				end
				for t in cd.supertypes do
					# Supertype (covariant)
					if t.need_anchor then covar_classes.add(t)
				end
				for pd in cd.mpropdefs do
					if exclude_private and pd.mproperty.visibility <= private_visibility then continue
					if pd isa MMethodDef then
						# Parameters (contravariant)
						for p in pd.msignature.mparameters do
							var t = p.mtype.undecorate
							if not t.need_anchor then
								# OK
							else if t isa MParameterType then
								contravar_pt.add(t)
							else if t isa MVirtualType then
								# TODO?
							else if t isa MClassType then
								contravar_classes.add(t)
							else
								abort
							end
						end
						# Return (covariant)
						var t = pd.msignature.return_mtype
						if t != null and t.need_anchor then
							t = t.undecorate
							if t isa MParameterType then
								covar_pt.add(t)
							else if t isa MVirtualType then
								# TODO?
							else if t isa MClassType then
								covar_classes.add(t)
							else
								abort
							end
						end
					else if pd isa MAttributeDef then
						# Attribute (invariant)
						var t = pd.static_mtype
						if t != null and t.need_anchor then
							t = t.undecorate
							if t isa MParameterType then
								covar_pt.add t
								contravar_pt.add t
							else if t isa MVirtualType then
								# TODO?
							else if t isa MClassType then
								covar_classes.add(t)
								contravar_classes.add(t)
							else
								abort
							end
						end
					else if pd isa MVirtualTypeDef then
						# Virtual type bound (covariant)
						var t = pd.bound
						if t != null and t.need_anchor then
							t = t.undecorate
							if t isa MParameterType then
								covar_pt.add t
							else if t isa MVirtualType then
								# TODO?
							else if t isa MClassType then
								covar_classes.add(t)
							else
								abort
							end
						end
					end
				end
			end
		end
	end

	# The set of all collected formal parameters
	var pts = new HashSet[MParameterType]

	# The set of generic types found in a covariant (and invariant) position
	var covar_classes = new HashSet[MClassType]

	# The set of formal parameters found in a covariant (and invariant) position
	var covar_pt = new HashSet[MParameterType]

	# The set of generic types found in a contravariant (and invariant) position
	var contravar_classes = new HashSet[MClassType]

	# The set of formal parameters found in a contravariant (and invariant) position
	var contravar_pt = new HashSet[MParameterType]

	# Classes by number of formal parameters
	var cpt_class = new Counter[String]

	# Does the collect exclude private properties?
	# Default is `false`
	var exclude_private = false

	# Propagate the variance constraints on `covar_classes`, `covar_pt`, `contravar_classes` and `contravar_pt`
	#
	# The algorithm uses a fixed-point approach on the covariance/contravariance rules.
	fun propagate
	do
		# Classes to add to the `covar_classes` set at the end of an iteration
		var new_covar = new Array[MClassType]
		# Classes to add to the `contravar_classes` set at the end of an iteration
		var new_contravar = new Array[MClassType]
		# Does a modification occurred, so that another iteration is needed?
		var dirty = true
		# Total number of iterations
		var cpt = 0

		while dirty do
			cpt += 1
			dirty = false
			new_covar.clear
			new_contravar.clear

			# 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].undecorate
				# The associated formal parameter
				var tp = c.mclass.mparameters[i]

				if not ta.need_anchor then
					# Nothing to do
				else if ta isa MParameterType then
					# COVAR * COVAR = COVAR
					if covar_pt.has(tp) and not covar_pt.has(ta) then
						covar_pt.add(ta)
						dirty = true
					end
					# COVAR * CONTRAVAR = CONTRAVAR
					if contravar_pt.has(tp) and not contravar_pt.has(ta) then
						contravar_pt.add(ta)
						dirty = true
					end
				else if ta isa MVirtualType then
					# TODO?
				else if ta isa MClassType then
					# COVAR * COVAR = COVAR
					if covar_pt.has(tp) and not covar_classes.has(ta) then
						new_covar.add ta
						dirty = true
					end
					# COVAR * CONTRAVAR = CONTRAVAR
					if contravar_pt.has(tp) and not contravar_classes.has(ta) then
						new_contravar.add ta
						dirty = true
					end
				end
			end

			# 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].undecorate
				# The associated formal parameter
				var tp = c.mclass.mparameters[i]

				if not ta.need_anchor then
					# Nothing to do
				else if ta isa MParameterType then
					# CONTRAVAR * CONTRAVAR = COVAR
					if contravar_pt.has(tp) and not covar_pt.has(ta) then
						covar_pt.add(ta)
						dirty = true
					end
					# CONTRAVAR * COVAR = CONTRAVAR
					if covar_pt.has(tp) and not contravar_pt.has(ta) then
						contravar_pt.add(ta)
						dirty = true
					end
				else if ta isa MVirtualType then
					# TODO?
				else if ta isa MClassType then
					# CONTRAVAR * CONTRAVAR = COVAR
					if contravar_pt.has(tp) and not covar_classes.has(ta) then
						new_covar.add ta
						dirty = true
					end
					# CONTRAVAR * COVAR = CONTRAVAR
					if covar_pt.has(tp) and not contravar_classes.has(ta) then
						new_contravar.add ta
						dirty = true
					end
				end
			end

			covar_classes.add_all(new_covar)
			contravar_classes.add_all(new_contravar)
		end
	end

	# Print the final stats on the screen
	fun print_stats
	do
		var nb_cov = 0
		var nb_con = 0
		var nb_inv = 0
		var nb_biv = 0

		for pt in pts do
			if covar_pt.has(pt) then
				if contravar_pt.has(pt) then
					nb_inv += 1
				else
					nb_cov += 1
					#print "covar: {pt.full_name}"
				end
			else
				if contravar_pt.has(pt) then
					nb_con += 1
					#print "contravar: {pt.full_name}"
				else
					nb_biv += 1
					#print "bivar: {pt.full_name}"
				end
			end
		end

		print "  covariants: {nb_cov} ({div(nb_cov*100, pts.length)}%)"
		print "  contravariants: {nb_con} ({div(nb_con*100, pts.length)}%)"
		print "  bivariants: {nb_biv} ({div(nb_biv*100, pts.length)}%)"
		print "  invariants: {nb_inv} ({div(nb_inv*100, pts.length)}%)"
		print "  total: {pts.length}"
	end
end
src/metrics/detect_variance_constraints.nit:79,1--333,3