Collect metrics about detected variances constraints on formal types.

In some OO-language that feature safe invariant generics by default, formal parameter types can annotated to be usable in covariant and contravariant positions. The price to pay is that the usage of formal parameter types in the class has constraints:

  • covariant-annotated formal types cannot be used in parameters or in attributes
  • contravariant-annotated formal types cannot be used in return or in attributes

This module provide a backward analysis that infers the possible variance annotations of formal types in Nit programs by identifying the existing constraints on the usage of those formal type.

It collects the types used in the signatures of properties then propagates constraints between types.

Introduced classes

class DetectVarianceConstraints

nitc :: DetectVarianceConstraints

A specific analysis that detects the variance constraints of formal parameters.

Redefined classes

redef class ToolContext

nitc :: detect_variance_constraints $ ToolContext

Global context for tools

All class definitions

class DetectVarianceConstraints

nitc $ DetectVarianceConstraints

A specific analysis that detects the variance constraints of formal parameters.
redef class ToolContext

nitc :: detect_variance_constraints $ ToolContext

Global context for tools
package_diagram nitc::detect_variance_constraints detect_variance_constraints nitc::metrics_base metrics_base nitc::detect_variance_constraints->nitc::metrics_base nitc::modelbuilder modelbuilder nitc::metrics_base->nitc::modelbuilder csv csv nitc::metrics_base->csv counter counter nitc::metrics_base->counter ...nitc::modelbuilder ... ...nitc::modelbuilder->nitc::modelbuilder ...csv ... ...csv->csv ...counter ... ...counter->counter nitc::metrics metrics nitc::metrics->nitc::detect_variance_constraints nitc::nitmetrics nitmetrics nitc::nitmetrics->nitc::metrics nitc::api_metrics api_metrics nitc::api_metrics->nitc::metrics nitc::nitmetrics... ... nitc::nitmetrics...->nitc::nitmetrics nitc::api_metrics... ... nitc::api_metrics...->nitc::api_metrics

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module array

core :: array

This module introduces the standard array structure.
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module caching

serialization :: caching

Services for caching serialization engines
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module console

console :: console

Defines some ANSI Terminal Control Escape Sequences.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module counter

counter :: counter

Simple numerical statistical analysis and presentation
module csv

csv :: csv

CSV document handling.
module digraph

graph :: digraph

Implementation of directed graphs, also called digraphs.
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module ini

ini :: ini

Read and write INI configuration files
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module lexer

nitc :: lexer

Lexer and its tokens.
module lexer_work

nitc :: lexer_work

Internal algorithm and data structures for the Nit lexer
module list

core :: list

This module handle double linked lists
module loader

nitc :: loader

Loading of Nit source files
module location

nitc :: location

Nit source-file and locations in source-file
module math

core :: math

Mathematical operations
module mdoc

nitc :: mdoc

Documentation of model entities
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module mmodule

nitc :: mmodule

modules and module hierarchies in the metamodel
module model

nitc :: model

Classes, types and properties
module model_base

nitc :: model_base

The abstract concept of model and related common things
module modelbuilder_base

nitc :: modelbuilder_base

Load nit source files and build the associated model
module more_collections

more_collections :: more_collections

Highly specific, but useful, collections-related classes.
module mpackage

nitc :: mpackage

Modelisation of a Nit package
module native

core :: native

Native structures for text and bytes
module nitpm_shared

nitc :: nitpm_shared

Services related to the Nit package manager
module numeric

core :: numeric

Advanced services for Numeric types
module opts

opts :: opts

Management of options on the command line
module ordered_tree

ordered_tree :: ordered_tree

Manipulation and presentation of ordered trees.
module parser

nitc :: parser

Parser.
module parser_nodes

nitc :: parser_nodes

AST nodes of the Nit language
module parser_prod

nitc :: parser_prod

Production AST nodes full definition.
module parser_work

nitc :: parser_work

Internal algorithm and data structures for the Nit parser
module phase

nitc :: phase

Phases of the processing of nit programs
module poset

poset :: poset

Pre order sets and partial order set (ie hierarchies)
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module tables

nitc :: tables

Module that interfaces the parsing tables.
module template

template :: template

Basic template system
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module toolcontext

nitc :: toolcontext

Common command-line tool infrastructure than handle options and error messages
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O
module version

nitc :: version

This file was generated by git-gen-version.sh

Parents

module metrics_base

nitc :: metrics_base

Helpers for various statistics tools.

Children

module metrics

nitc :: metrics

Various statistics about Nit models and programs

Descendants

module a_star-m

a_star-m

module api

nitc :: api

Components required to build a web server about the nit model.
module nitmetrics

nitc :: nitmetrics

A program that collects various metrics on nit programs and libraries
module nitweb

nitc :: nitweb

Runs a webserver based on nitcorn that render things from model.
# Collect metrics about detected variances constraints on formal types.
#
# In some OO-language that feature safe invariant generics by default,
# formal parameter types can annotated to be usable in covariant and contravariant positions.
# The price to pay is that the usage of formal parameter types in the class has constraints:
#
# * covariant-annotated formal types cannot be used in parameters or in attributes
# * contravariant-annotated formal types cannot be used in return or in attributes
#
# This module provide a backward analysis that infers the possible variance annotations
# of formal types in Nit programs by identifying the existing constraints on the usage of those formal type.
#
# It collects the types used in the signatures of properties then propagates constraints between types.
module detect_variance_constraints

import model
import metrics_base

redef class ToolContext
	# --detect-variance-constraints
	var opt_detect_variance_constraints = new OptionBool("Detect the definition-site variance constraints on formal parameters", "--detect-variance-constraints")

	# The DetectVarianceConstraints phase
	var detect_variance_constraints_phase: Phase = new DetectVarianceConstraintsPhase(self, null)
end

private class DetectVarianceConstraintsPhase
	super Phase

	init
	do
		toolcontext.option_context.add_option(toolcontext.opt_detect_variance_constraints)
	end

	redef fun process_mainmodule(mainmodule, given_mmodules)
	do
		if not toolcontext.opt_detect_variance_constraints.value and not toolcontext.opt_all.value then return

		print "--- Detection of variance constraints on formal parameter types ---"

		var k = new DetectVarianceConstraints
		k.collect(mainmodule)

		print "-- Generic classes --"
		k.cpt_class.print_elements(10)
		print "  total classes: {k.cpt_class.sum}"
		print "  total formal parameters: {k.pts.length}"

		k.propagate

		print "-- Including `private` properties --"
		k.print_stats

		k = new DetectVarianceConstraints
		k.exclude_private = true
		k.collect(mainmodule)

		k.propagate

		print "-- Excluding `private` properties --"
		k.print_stats
	end
end

# 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:15,1--333,3