# inference mechanisms
from pyrser.error import *
from pyrser.type_system.scope import Scope
from pyrser.type_system.evalctx import EvalCtx
from pyrser.type_system.val import Val
from pyrser.passes.to_yml import *
[docs]class InferNode:
"""
An instance of this class is automatically attach on each
AST Nodes by the inference algorithm during the inference process
into the '.infer_node' attribute.
The aim is to keep track of the correct Scope and the final EvalCtx
for each AST Nodes.
"""
[docs] def __init__(self, init_scope: Scope=None, parent: Scope=None):
if init_scope is not None:
self.scope_node = init_scope
else:
self.scope_node = Scope(is_namespace=False)
if parent is not None:
self.scope_node.set_parent(parent.scope_node)
self.type_node = None
self.need_feedback = False
[docs]class Inference:
# TO REDEFINE IN AST
[docs] def type_algos(self) -> ('self.infer_algo',
'self.feedback_algo', ['args']):
"""
Sub class must return a Tuple of 3 elements:
- the method to use to infer type.
- the list of params to used when infer a type.
- the method to use when feedback a type.
This is useful to connect AST members to generic algo or
to overload some specific semantic for your language.
"""
raise Exception(("%s must inherit from Inference and define" +
" type_algos(self) method to support" +
" Pyrser Type Systems.") % type(self).__name__)
[docs] def infer_type(self, init_scope: Scope=None, diagnostic=None):
"""
Do inference. Write infos into diagnostic object, if this parameter
is not provide and self is a AST (has is own diagnostic object),
use the diagnostic of self.
"""
# create the first .infer_node
if not hasattr(self, 'infer_node'):
self.infer_node = InferNode(init_scope)
elif init_scope is not None:
# only change the root scope
self.infer_node.scope_node = init_scope
# get algo
type_algo = self.type_algos()
if diagnostic is None and hasattr(self, 'diagnostic'):
diagnostic = self.diagnostic
type_algo[0](type_algo[1], diagnostic)
[docs] def feedback(self, diagnostic=None):
"""
Do feedback. Write infos into diagnostic object, if this parameter
is not provide and self is a AST (has is own diagnostic object),
use the diagnostic of self.
"""
# get algo
type_algo = self.type_algos()
if diagnostic is None and hasattr(self, 'diagnostic'):
diagnostic = self.diagnostic
type_algo[2](diagnostic)
## INFER ALGOS
[docs] def infer_block(self, body, diagnostic=None):
"""
Infer type on block is to type each of is sub-element
"""
# RootBlockStmt has his own .infer_node (created via infer_type)
for e in body:
e.infer_node = InferNode(parent=self.infer_node)
e.infer_type(diagnostic=diagnostic)
# TODO: result in .infer_node.type_node
[docs] def infer_subexpr(self, expr, diagnostic=None):
"""
Infer type on the subexpr
"""
expr.infer_node = InferNode(parent=self.infer_node)
expr.infer_type(diagnostic=diagnostic)
# TODO: result in .infer_node.type_node
[docs] def infer_fun(self, args, diagnostic=None):
# 0 - get function
call_expr, arguments = args
diagnostic.notify(
Severity.INFO,
"Infer Function call '%s'" % call_expr.value,
self.info
)
# 1 - fetch all possible types for the call expr
call_expr.infer_node = InferNode(parent=self.infer_node)
call_expr.infer_type(diagnostic=diagnostic)
f = call_expr.infer_node.scope_node
# 2 - fetch all possible types for each parameters
tparams = []
i = 0
for p in arguments:
p.infer_node = InferNode(parent=self.infer_node)
p.infer_type(diagnostic=diagnostic)
i += 1
tparams.append(p.infer_node.scope_node)
# 3 - select overloads
(final_call_expr, final_tparams) = f.get_by_params(tparams)
# 4 - record overloads
self.infer_node.scope_node.clear()
self.infer_node.scope_node.update(final_call_expr)
nversion = len(final_call_expr)
if nversion > 1:
# too many choice
self.infer_node.need_feedback = True
return
elif nversion == 0:
# TODO: Try type reconstruction
# if final_tparams[x][0].is_polymorphic
# ...
# type error
details = "\ndetails:\n"
details += "overloads:\n"
for fun in f.values():
details += str(fun.get_compute_sig()) + "\n"
details += "parameters:\n"
i = 0
# TODO: change by final_tparams
for p in tparams:
details += "- param[%d] type " % i
ptypes = []
for alt in p.values():
ptypes.append(alt.tret)
details += '|'.join(ptypes) + "\n"
i += 1
# TODO: when f are empty
diagnostic.notify(
Severity.ERROR,
("can't match overloads "
+ "with parameters for function '%s'") % str(f.first().name),
self.info,
details
)
return
# here len(self.type_node) == 1 && len(final_tparams) == 1
# 5 - handle polymorphism
# TODO: INSIDE A CLASS?
my_map = dict()
my_type = self.infer_node.scope_node.first()
if my_type.tret.is_polymorphic:
diagnostic.notify(
Severity.INFO,
"polymorphic return type %s" % str(my_type.get_compute_sig()),
self.info
)
# if tret is polymorphic, take type from call_expr if unique,
# else type come from parameter resolution
sig = call_expr.infer_node.scope_node.first()
if (len(call_expr.infer_node.scope_node) == 1
and not sig.tret.is_polymorphic
):
diagnostic.notify(
Severity.INFO,
"call_expr have real type '%s'" % sig.tret,
self.info
)
my_map.update(sig.resolution)
my_type.set_resolved_name(
sig.resolution,
my_type.tret,
sig.tret
)
arity = len(my_type.tparams)
for i in range(arity):
p = my_type.tparams[i]
# use AST Injector
if final_tparams[0][i].first()._translate_to is not None:
t = final_tparams[0][i].first()._translate_to
old = arguments[i]
n = t.notify
diagnostic.notify(
n.severity,
n.msg,
old.info,
details=n.details
)
arguments[i] = self.infer_node.scope_node.callInjector(old, t)
infer_node = InferNode(parent=self.infer_node)
infer_node.scope_node.add(t.fun)
old.infer_node.scope_node.set_parent(infer_node.scope_node)
arguments[i].infer_node = infer_node
if p.is_polymorphic:
# take type in final_tparams
diagnostic.notify(
Severity.INFO,
"- param[%d] polymorphic type" % i, arguments[i].info
)
# here len(final_tparams) == 1, only one set for parameters
sig = final_tparams[0][i].first()
if not sig.tret.is_polymorphic:
diagnostic.notify(
Severity.INFO,
"- argument[%d] real type '%s'" % (i, sig.tret),
arguments[i].info
)
my_map[p.value] = sig.resolution[sig.tret.value]
my_type.set_resolved_name(sig.resolution, p, sig.tret)
diagnostic.notify(
Severity.INFO,
"after resolution %s" % str(my_type.get_compute_sig()),
self.info
)
# 6 - feedback
self.map_type = my_map
self.final_type = my_type.tret
self.feedback(diagnostic)
# 7 - Are we finish? Global type reconstruction
# TODO: result in .infer_node.type_node
[docs] def infer_id(self, ident, diagnostic=None):
"""
Infer type from an ID!
- check if ID is declarated in the scope
- if no ID is polymorphic type
"""
# check if ID is declared
#defined = self.type_node.get_by_symbol_name(ident)
defined = self.infer_node.scope_node.get_by_symbol_name(ident)
if len(defined) > 0:
# set from matchings declarations
#self.type_node.update(defined)
self.infer_node.scope_node.update(defined)
else:
diagnostic.notify(
Severity.ERROR,
"%s never declared" % self.value,
self.info
)
# TODO: uncomment/fix when finish
#def infer_id_self_type(self, ident, diagnostic=None):
# """
# Infer type from an ID!
# - check if ID is declarated in the scope
# - if no ID is polymorphic type
# """
# # check if ID is declared
# defined = self.type_node.get_by_symbol_name(ident)
# if len(defined) > 0:
# # set from matchings declarations
# self.type_node.update(defined)
# else:
# # TODO: est de type polymorphique local ... pas tout le temps ?1
# # a faire dans une declaration ... ou a l'affectation
# # mais pas la
# self.type_node.add(Var(ident, '?1'))
# self.type_node.need_feedback = True
[docs] def infer_literal(self, args, diagnostic=None):
"""
Infer type from an LITERAL!
Type of literal depend of language.
We adopt a basic convention
"""
literal, t = args
#self.type_node.add(EvalCtx.from_sig(Val(literal, t)))
self.infer_node.scope_node.add(EvalCtx.from_sig(Val(literal, t)))
## FEEDBACK ALGOS
[docs] def feedback_block(self, diagnostic=None):
# normally nothing to do!?!?!
type_algo = self.type_algos()
# body in type_algo[1]
for e in type_algo[1]:
if e.infer_node.need_feedback:
e.feedback(diagnostic)
[docs] def feedback_subexpr(self, diagnostic=None):
final_scn = self.infer_node.scope_node
final_scn = final_scn.get_by_return_type(self.final_type)
self.infer_node.scope_node = final_scn
self.infer_node.need_feedback = False
if self.expr.infer_node.need_feedback:
self.expr.feedback(diagnostic)
[docs] def feedback_leaf(self, diagnostic=None):
final_scn = self.infer_node.scope_node
final_scn = final_scn.get_by_return_type(self.final_type)
self.infer_node.scope_node = final_scn
self.infer_node.need_feedback = False
[docs] def feedback_fun(self, diagnostic=None):
final_scn = self.infer_node.scope_node
final_scn = final_scn.get_by_return_type(self.final_type)
self.infer_node.scope_node = final_scn
self.infer_node.need_feedback = False
arguments = self.infer_node.scope_node.first().tparams
nargs = len(arguments)
type_algo = self.type_algos()
call_expr, args = type_algo[1]
if call_expr.infer_node.need_feedback:
diagnostic.notify(
Severity.INFO,
"feed back call_expr = %s" % self.final_type,
call_expr.info
)
call_expr.map_type = self.map_type
# TODO: could be infer_node.type_node
call_expr.final_type = self.final_type
call_expr.feedback()
for i in range(nargs):
p = arguments[i]
if args[i].infer_node.need_feedback:
diagnostic.notify(
Severity.INFO,
"feed back p[%d] = %s" % (i, p),
args[i].info
)
diagnostic.notify(
Severity.INFO,
"feed map %s" % self.map_type,
args[i].info
)
args[i].map_type = self.map_type
args[i].final_type = p
args[i].feedback(diagnostic)
[docs] def feedback_id(self, diagnostic=None):
# instancie META!!!
if len(self.infer_node.scope_node) > 1:
final_scn = self.infer_node.scope_node
final_scn = final_scn.get_by_return_type(self.final_type)
self.infer_node.scope_node = final_scn
if len(self.infer_node.scope_node) != 1:
# ERROR TYPE !!!?!?
diagnostic.notify(
Severity.ERROR,
("Type error: too many candidates %s"
% str(self.infer_node.scope_node)),
self.info
)
else:
the_sig = list(self.infer_node.scope_node.values())[0]
if the_sig.tret.is_polymorphic:
# self.final_type)
self.infer_node.type_node = EvalCtx.from_sig(the_sig)
final_tn = self.infer_node.type_node
final_tn.set_resolved_name(
self.map_type,
the_sig.tret,
self.final_type
)