feat(gen): generic calls with explicit args and pointer-parameter inference
Three gaps kept `mod.f[T](x)` from compiling: - get_func_name now unwraps a Subscript callee to its base name, so an expression statement like `f[int](x)` / `mod.f[int](x)` reaches the normal call path instead of failing name resolution. Generic class constructors and type subscripts still return None, keeping their dedicated paths. - the generic-call dispatcher resolves `Attribute` bases inside the subscript (`temp.array2list[int]`) to the defining module's SHA1, and treats it as a module-qualified template lookup. - inference understands pointer/array annotations around the type parameter: `_annotation_refers_type_param` walks PtrT/ArrayT wrappers and tuple slices, and inference peels as many pointer/array layers off the argument as the parameter annotation wraps around T, so `array2list(l, ...)` with `array: T*` and `l: []int` infers int. Self-host: 110/110 with the new compiler building itself; GenericTest, GenericTest3, GenericTest4, VectorGenericTest and ListTest pass.
This commit is contained in:
1 parent
40e3388dc3
commit
f57a7d962c
4 files changed
+121
-5
No files matched your search
@@ -1016,6 +1016,18 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
|
||||
nm_a: ast.Name* = ast.Name*(node)
|
||||
if nm_a.id is not None and string.strcmp(nm_a.id, tp_name) == 0: return 1
|
||||
return 0
|
||||
if k == ast.ASTKind.Subscript:
|
||||
# `T*` / `[]T` parse to PtrT[T] / ArrayT[T, N]; the type parameter sits in the slice.
|
||||
sub_a: ast.Subscript* = ast.Subscript*(node)
|
||||
return self._annotation_refers_type_param(sub_a.slice, tp_name)
|
||||
if k == ast.ASTKind.Tuple:
|
||||
# ArrayT[T, N]: the element type is the first element, the length the second.
|
||||
tup_a: ast.Tuple* = ast.Tuple*(node)
|
||||
if tup_a.elts is None: return 0
|
||||
tn_a: sizet = len(tup_a.elts)
|
||||
for ti_a in range(int(tn_a)):
|
||||
if self._annotation_refers_type_param(tup_a.elts.get(ti_a), tp_name) != 0: return 1
|
||||
return 0
|
||||
if k == ast.ASTKind.BinOp:
|
||||
bop_a: ast.BinOp* = ast.BinOp*(node)
|
||||
if self._annotation_refers_type_param(bop_a.left, tp_name) != 0: return 1
|
||||
|
||||
@@ -39,10 +39,20 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||||
fk: int = cl.func.kind()
|
||||
if fk == ast.ASTKind.Subscript:
|
||||
sub: ast.Subscript* = ast.Subscript*(cl.func)
|
||||
explicit_slice = sub.slice
|
||||
if sub.value is not None and sub.value.kind() == ast.ASTKind.Name:
|
||||
sn: ast.Name* = ast.Name*(sub.value)
|
||||
tpl_name = sn.id
|
||||
explicit_slice = sub.slice
|
||||
elif sub.value is not None and sub.value.kind() == ast.ASTKind.Attribute:
|
||||
# `mod.f[T](...)`: the template lives in the imported module.
|
||||
aq: ast.Attribute* = ast.Attribute*(sub.value)
|
||||
if aq.attr is not None and aq.value is not None and aq.value.kind() == ast.ASTKind.Name:
|
||||
mq: ast.Name* = ast.Name*(aq.value)
|
||||
if mq.id is not None and trans._imported_modules is not None:
|
||||
if self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mq.id) != 0: tpl_mod_sha1 = self._lookup_module_sha1(mq.id)
|
||||
if tpl_mod_sha1 is not None:
|
||||
is_attr_call = 1
|
||||
tpl_name = aq.attr
|
||||
elif fk == ast.ASTKind.Name: tpl_name = func_name
|
||||
elif fk == ast.ASTKind.Attribute:
|
||||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||||
@@ -97,13 +107,14 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||||
tp_name: char* = tp_names.get(tpi)
|
||||
ta: char* = None
|
||||
slot: int = self.Gen.ClassGenGeneric._find_generic_func_param_slot(template_fd, tp_name)
|
||||
if slot >= 0 and sizet(slot) < can: ta = self.Gen.ClassGenGeneric._infer_generic_func_type_arg(pool, cargs.get(sizet(slot)), tpl_name, trans)
|
||||
pann: ast.AST* = self.Gen.ClassGenGeneric._generic_func_param_annot(template_fd, slot)
|
||||
if slot >= 0 and sizet(slot) < can: ta = self.Gen.ClassGenGeneric._infer_generic_func_type_arg(pool, cargs.get(sizet(slot)), tpl_name, trans, tp_name, pann)
|
||||
if ta is None and slot >= 0:
|
||||
# Keyword arg: locate by template param name (positional wins; -1 if slot taken)
|
||||
kwi: int = self._generic_func_kw_arg_index(template_fd, slot, cl.keywords, int(can))
|
||||
if kwi >= 0:
|
||||
kwn: ast.Keyword* = ast.Keyword*(cl.keywords.get(sizet(kwi)))
|
||||
if kwn is not None and kwn.value is not None: ta = self.Gen.ClassGenGeneric._infer_generic_func_type_arg(pool, kwn.value, tpl_name, trans)
|
||||
if kwn is not None and kwn.value is not None: ta = self.Gen.ClassGenGeneric._infer_generic_func_type_arg(pool, kwn.value, tpl_name, trans, tp_name, pann)
|
||||
if ta is None and ctx_args is not None and sizet(tpi) < len(ctx_args): ta = ctx_args.get(tpi)
|
||||
if ta is None:
|
||||
self.Gen.TypeGenErr.fatal_error(cl, f"Cannot infer type argument {tpi + 1} of generic function '{tpl_name}' from the arguments (type parameter {tp_name});" f"silent default to int is forbidden. Annotate explicitly, e.g. {tpl_name}[Type](...), or annotate the corresponding argument", "TypeError")
|
||||
|
||||
@@ -157,6 +157,19 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
|
||||
if knm is not None and string.strcmp(knm, key) == 0: return nodes_local.get(i)
|
||||
return None
|
||||
|
||||
def _has_generic_func_named(self, name: str) -> int:
|
||||
"""1 when any module registered a generic function template with this bare name."""
|
||||
if name is None or self.GenericFuncKeys is None: return 0
|
||||
keys_local: list[str]* = self.GenericFuncKeys
|
||||
kn: sizet = len(keys_local)
|
||||
for i in range(int(kn)):
|
||||
knm: str = keys_local.get(i)
|
||||
if knm is None: continue
|
||||
dot: str = string.strrchr(knm, '.')
|
||||
if dot is None: continue
|
||||
if string.strcmp(dot + 1, name) == 0: return 1
|
||||
return 0
|
||||
|
||||
def _find_generic_func_template_sha1(self, key: str) -> str:
|
||||
"""Find a generic function template's source module SHA1 by key; None when absent."""
|
||||
if self.GenericFuncKeys is None or key is None: return None
|
||||
@@ -227,9 +240,79 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
|
||||
if self.Gen.CallGenCtor._annotation_refers_type_param(ag.annotation, tp_name) != 0: return int(ai)
|
||||
return -1
|
||||
|
||||
def _infer_generic_func_type_arg(self, pool: memhub.MemBuddy*, arg_node: ast.AST*, func_name: str, trans: HT.Translator*) -> str:
|
||||
def _generic_func_param_annot(self, fd: ast.FunctionDef*, slot: int) -> ast.AST*:
|
||||
"""Annotation of 0-based parameter slot; None when absent."""
|
||||
if fd is None or slot < 0 or fd.args is None: return None
|
||||
if fd.args.kind() != ast.ASTKind.Arguments: return None
|
||||
ags: ast.Arguments* = ast.Arguments*(fd.args)
|
||||
if ags.args is None or sizet(slot) >= len(ags.args): return None
|
||||
pa: ast.AST* = ags.args.get(sizet(slot))
|
||||
if pa is None or pa.kind() != ast.ASTKind.Arg: return None
|
||||
return ast.Arg*(pa).annotation
|
||||
|
||||
def _ptr_wrap_depth(self, ann: ast.AST*, tp_name: str) -> int:
|
||||
"""Pointer/array layers wrapping bare tp_name (`T*` -> 1, `[]T` -> 1); -1 when not a wrapper."""
|
||||
if ann is None or tp_name is None: return -1
|
||||
k: int = ann.kind()
|
||||
if k == ast.ASTKind.Name:
|
||||
nm: ast.Name* = ast.Name*(ann)
|
||||
if nm.id is not None and string.strcmp(nm.id, tp_name) == 0: return 0
|
||||
return -1
|
||||
if k != ast.ASTKind.Subscript: return -1
|
||||
sub: ast.Subscript* = ast.Subscript*(ann)
|
||||
if sub.value is None or sub.slice is None: return -1
|
||||
wn: str = None
|
||||
if sub.value.kind() == ast.ASTKind.Name: wn = ast.Name*(sub.value).id
|
||||
elif sub.value.kind() == ast.ASTKind.Attribute: wn = ast.Attribute*(sub.value).attr
|
||||
if wn is None: return -1
|
||||
if string.strcmp(wn, "ArrayT") == 0:
|
||||
# []T / [N]T: element type first, length second.
|
||||
if sub.slice.kind() != ast.ASTKind.Tuple: return -1
|
||||
tup: ast.Tuple* = ast.Tuple*(sub.slice)
|
||||
if tup.elts is None or len(tup.elts) == 0: return -1
|
||||
inner_a: int = self._ptr_wrap_depth(tup.elts.get(0), tp_name)
|
||||
if inner_a < 0: return -1
|
||||
return inner_a + 1
|
||||
inner: int = self._ptr_wrap_depth(sub.slice, tp_name)
|
||||
if inner < 0: return -1
|
||||
if string.strcmp(wn, "PtrT") == 0: return inner + 1
|
||||
# Descriptor / value-reference wrappers (ConstT / RefT / TempRefT) add no pointer layer.
|
||||
return inner
|
||||
|
||||
def _infer_arg_pointee_name(self, pool: memhub.MemBuddy*, arg_node: ast.AST*, strip: int, trans: HT.Translator*) -> str:
|
||||
"""Type name reached by peeling `strip` pointer/array layers off a Name argument."""
|
||||
if arg_node is None or strip <= 0 or trans is None: return None
|
||||
if arg_node.kind() != ast.ASTKind.Name: return None
|
||||
nm: ast.Name* = ast.Name*(arg_node)
|
||||
if nm.id is None: return None
|
||||
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
||||
if ve is None or ve.Spec is None: return None
|
||||
sp: TSpec.TypeSpec* = ve.Spec
|
||||
d: int = strip
|
||||
while d > 0 and sp is not None:
|
||||
if sp.Kind == TSpec.TS_ARRAY or sp.Kind == TSpec.TS_PTR: sp = sp.Elem
|
||||
else: return None
|
||||
d -= 1
|
||||
if sp is None: return None
|
||||
txt: str = self._infer_scalar_type_name_from_spec(pool, sp)
|
||||
if txt is not None: return txt
|
||||
cn: str = sp.SourceName
|
||||
if cn is None: cn = sp.BaseName
|
||||
if cn is None: return None
|
||||
fi: str = None
|
||||
if trans is not None: fi = trans._from_imports
|
||||
return self.Gen.CallGenCtor._ptr_layer_type_arg(pool, cn, sp.PtrLevel, fi)
|
||||
|
||||
def _infer_generic_func_type_arg(self, pool: memhub.MemBuddy*, arg_node: ast.AST*, func_name: str, trans: HT.Translator*, tp_name: str = None, param_annot: ast.AST* = None) -> str:
|
||||
"""Infer a generic function's type argument name from a call argument AST; None when unknown."""
|
||||
if arg_node is None: return None
|
||||
# `T*` / `[]T` parameters: the argument's pointer/array layer is the decayed parameter,
|
||||
# not part of T. Peel exactly as many layers as the annotation wraps around T.
|
||||
if tp_name is not None and param_annot is not None:
|
||||
strip: int = self._ptr_wrap_depth(param_annot, tp_name)
|
||||
if strip > 0:
|
||||
pointee: str = self._infer_arg_pointee_name(pool, arg_node, strip, trans)
|
||||
if pointee is not None: return pointee
|
||||
k: int = arg_node.kind()
|
||||
if k == ast.ASTKind.Constant:
|
||||
cn: ast.Constant* = ast.Constant*(arg_node)
|
||||
@@ -240,7 +323,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
|
||||
# Unary +/- constant (clamp(-3, ...)): the type is determined by the operand
|
||||
if k == ast.ASTKind.UnaryOp:
|
||||
uo: ast.UnaryOp* = ast.UnaryOp*(arg_node)
|
||||
return self._infer_generic_func_type_arg(pool, uo.operand, func_name, trans)
|
||||
return self._infer_generic_func_type_arg(pool, uo.operand, func_name, trans, tp_name, param_annot)
|
||||
if k == ast.ASTKind.Name:
|
||||
nm: ast.Name* = ast.Name*(arg_node)
|
||||
if nm.id is not None and trans is not None:
|
||||
|
||||
@@ -51,6 +51,16 @@ novirtual class ExprGenBase:
|
||||
elif k == ast.ASTKind.Attribute:
|
||||
at: ast.Attribute* = ast.Attribute*(func_node)
|
||||
return at.attr
|
||||
elif k == ast.ASTKind.Subscript:
|
||||
# `f[T](...)` / `mod.f[T](...)`: the callable name lives in the subscript base.
|
||||
# Generic class constructors and type subscripts (`list[int](pool)`, `rawptr[T](x)`)
|
||||
# must keep returning None so their dedicated paths still run.
|
||||
sub: ast.Subscript* = ast.Subscript*(func_node)
|
||||
base: str = self.get_func_name(sub.value)
|
||||
if base is None: return None
|
||||
if self.Gen.ClassGenGeneric._find_generic_template(base) is not None: return None
|
||||
if self.Gen.ClassGenGeneric._has_generic_func_named(base) != 0: return base
|
||||
return None
|
||||
return None
|
||||
# Escape an LLVM IR string literal.
|
||||
def escape_llvm_string(self, pool: memhub.MemBuddy*, src: str) -> str:
|
||||
|
||||
Reference in new issue
Block a user