diff --git a/App/lib/core/Gen/Call/CallGenCtor.vp b/App/lib/core/Gen/Call/CallGenCtor.vp index b98e6f8..fa80d49 100644 --- a/App/lib/core/Gen/Call/CallGenCtor.vp +++ b/App/lib/core/Gen/Call/CallGenCtor.vp @@ -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 diff --git a/App/lib/core/Gen/Call/CallGenDispatch.vp b/App/lib/core/Gen/Call/CallGenDispatch.vp index ab08184..9e71574 100644 --- a/App/lib/core/Gen/Call/CallGenDispatch.vp +++ b/App/lib/core/Gen/Call/CallGenDispatch.vp @@ -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") diff --git a/App/lib/core/Gen/Class/ClassGenGeneric.vp b/App/lib/core/Gen/Class/ClassGenGeneric.vp index 37c3d33..afd3f95 100644 --- a/App/lib/core/Gen/Class/ClassGenGeneric.vp +++ b/App/lib/core/Gen/Class/ClassGenGeneric.vp @@ -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: diff --git a/App/lib/core/Gen/Expr/ExprGenBase.vp b/App/lib/core/Gen/Expr/ExprGenBase.vp index ece4142..6c77031 100644 --- a/App/lib/core/Gen/Expr/ExprGenBase.vp +++ b/App/lib/core/Gen/Expr/ExprGenBase.vp @@ -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: