From 6d0853beb65ddb7f94f3b1ef376ba7a3490c2f5a Mon Sep 17 00:00:00 2001 From: zerbina <100542850+zerbina@users.noreply.github.com> Date: Tue, 25 Apr 2023 22:37:16 +0100 Subject: [PATCH] sigmatch: simpler procedural type matching Summary ======= * simplify the logic for handling generic routines using `auto` as the return types * reject un-instantiated routines earlier when used in branch and array constructor expressions, resulting in clearer error messages (`has no concrete type` instead of `cannot instantiate`) * support lambda expressions coming from template expansions Details ======= Introduced by https://github.com/nim-lang/Nim/pull/3234, when both the formal and actual type are unresolved meta types, the relation is considered to be `isBothMetaConvertible`. The intention seems to have been supporting passing `auto`-returning routines or lambda-expressions to generic procedure-type parameters, but the logic introduced by the aforementioned PR is not necessary for achieving that. `auto` return types now use dedicated handling in `procTypeRel`, both the formal and actual type being unresolved generics results in a mismatch again, and `typeRel` is no longer applied repeatedly for `isBothMetaConvertible` matches. There was also the issue of `generateTypeInstance` being misused, which could result in spurious `cannot instantiate` or internal compiler errors. Since meta types are possibly involved, the correct routine to use is `prepareMetatypeForSigmatch`. Testing that the produced type instance is not a meta type was also done incorrectly (`isMetaType` was used instead of `containsGenericType`). Finally, `nkStmtListExpr` nodes are now considered when instantiating a generic routine as part of parameter matching, which allows for complex expressions yielding un-instantiated generic routines (e.g.: `call((discard x; genericRoutine))`). --- compiler/sem/sem.nim | 17 ++ compiler/sem/semexprs.nim | 1 + compiler/sem/semstmts.nim | 1 + compiler/sem/semtypinst.nim | 4 + compiler/sem/sigmatch.nim | 165 +++++++++++------- .../tgeneric_intantiation_outside_call.nim | 2 +- .../tgeneric_procedure_cannot_assigned.nim | 2 +- .../overload/tproc_type_inference.nim | 95 ++++++++++ 8 files changed, 223 insertions(+), 64 deletions(-) create mode 100644 tests/lang_callable/overload/tproc_type_inference.nim diff --git a/compiler/sem/sem.nim b/compiler/sem/sem.nim index 765b37f6b14..cc0af1410d6 100644 --- a/compiler/sem/sem.nim +++ b/compiler/sem/sem.nim @@ -243,6 +243,23 @@ proc fitNodeConsiderViewType(c: PContext, formal: PType, arg: PNode; info: TLine else: result = a +proc genericProcCheck(c: PContext, n: PNode): PNode = + ## Checks that the analysed expression `n` is of generic procedural type. + ## Returns either `n` or an error. + if n.typ != nil and n.typ.kind == tyError: + return n + + # skip all statement list wrappers: + var it {.cursor.} = n + while it.kind == nkStmtListExpr: + it = it.lastSon + + if (it.kind == nkSym and it.sym.isGenericRoutineStrict) or + it.isGenericRoutine: + c.config.newError(n, PAstDiag(kind: adSemProcHasNoConcreteType)) + else: + n + proc inferWithMetatype(c: PContext, formal: PType, arg: PNode, coerceDistincts = false): PNode diff --git a/compiler/sem/semexprs.nim b/compiler/sem/semexprs.nim index 80d7159af16..c136cdc0dfb 100644 --- a/compiler/sem/semexprs.nim +++ b/compiler/sem/semexprs.nim @@ -782,6 +782,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = if e.kind != nkError: e = semExprWithType(c, e, {}) + e = genericProcCheck(c, e) if typ.isNil: # must be the first item; initialize the common type: diff --git a/compiler/sem/semstmts.nim b/compiler/sem/semstmts.nim index 40a7a948803..873fe9cd050 100644 --- a/compiler/sem/semstmts.nim +++ b/compiler/sem/semstmts.nim @@ -80,6 +80,7 @@ proc semProc(c: PContext, n: PNode): PNode proc semExprBranch(c: PContext, n: PNode; flags: TExprFlags = {}): PNode = result = semExpr(c, n, flags) + result = genericProcCheck(c, result) if result.typ != nil: # XXX tyGenericInst here? if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) diff --git a/compiler/sem/semtypinst.nim b/compiler/sem/semtypinst.nim index 96c9a3df7c4..ca92665d473 100644 --- a/compiler/sem/semtypinst.nim +++ b/compiler/sem/semtypinst.nim @@ -793,6 +793,10 @@ proc recomputeFieldPositions*(t: PType; obj: PNode; currPosition: var int) = proc generateTypeInstance*(p: PContext, pt: TIdTable, info: TLineInfo, t: PType): PType = + ## Produces the instantiated type for the generic type `t`, using the + ## bindings provided by `pt`. All type variables used by `t` must have + ## *concrete* type bounds to them -- both meta types and missing bindings + ## are disallowed and will result in an instantiation failure. # Given `t` like Foo[T] # pt: Table with type mappings: T -> int # Desired result: Foo[int] diff --git a/compiler/sem/sigmatch.nim b/compiler/sem/sigmatch.nim index 6b9b2190ed5..679b9a02fae 100644 --- a/compiler/sem/sigmatch.nim +++ b/compiler/sem/sigmatch.nim @@ -41,6 +41,7 @@ import ], compiler/utils/[ debugutils, + idioms ] # xxx: reports are a code smell meaning data types are misplaced, for example @@ -509,20 +510,20 @@ proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = ## For example we have: ## ## .. code-block:: nim - ## proc myMap[T,S](sIn: seq[T], f: proc(x: T): S): seq[S] = ... - ## proc innerProc[Q,W](q: Q): W = ... + ## proc myMap[T,U,S](sIn: seq[T], f: proc(x: T, y: U): S): seq[S] = ... + ## proc innerProc[Q](q: Q, b: Q): auto = ... ## ## And we want to match: myMap(@[1,2,3], innerProc) ## This proc (procParamTypeRel) will do the following steps in - ## three different calls: - ## - matches f=T to a=Q. Since f is metatype, we resolve it - ## to int (which is already known at this point). So in this case - ## Q=int mapping will be saved to c.bindings. - ## - matches f=S to a=W. Both of these metatypes are unknown, so we - ## return with isBothMetaConvertible to ask for rerun. - ## - matches f=S to a=W. At this point the return type of innerProc - ## is known (we get it from c.bindings). We can use that value - ## to match with f, and save back to c.bindings. + ## two different calls: + ## - matches `f`=T to `a`=Q. `f` is a resolved metatype ('int' is bound to + ## it already), so `a` can be inferred; a binding for Q=int is saved + ## - matces `f`=U to `a`=Q. `f` is an unresolved metatype, but since Q was + ## already inferred as 'int', U can be inferred from it; a binding for + ## U=int is saved + ## + ## The 'auto' return type doesn't reach here, but is instead handled by + ## ``procTypeRel``. var f = f a = a @@ -531,6 +532,7 @@ proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = let aResolved = PType(idTableGet(c.bindings, a)) if aResolved != nil: a = aResolved + if a.isMetaType: if f.isMetaType: # We are matching a generic proc (as proc param) @@ -539,12 +541,14 @@ proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = # type is already fully-determined, so we are # going to try resolve it if c.call != nil: - f = generateTypeInstance(c.c, c.bindings, c.call.info, f) + f = prepareMetatypeForSigmatch(c.c, c.bindings, c.call.info, f) else: f = nil - if f.isNil() or f.isMetaType: - # no luck resolving the type, so the inference fails - return isBothMetaConvertible + if f.isNil() or containsGenericType(f): + # no luck resolving the type, so the inference fails. Note that using + # ``isMetaType`` wont work, as it doesn't check for nested + # unresolved type variables + return isNone # Note that this typeRel call will save a's resolved type into c.bindings let reverseRel = typeRel(c, a, f) if reverseRel >= isGeneric: @@ -577,12 +581,16 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = for i in 1..