From 8cd5f1f8f53e9b286c7ca5af5245f20c052d47eb Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 2 Aug 2016 01:15:46 +0300 Subject: [PATCH] introduce tyInferred for the unbound concept type params * Why is tyInferred needed? The bindings in TCandidate are capable of inferring types within a single call expression. In concepts, we need to infer types in the same way, but across the whole body of the concept. Previously, once a concept type param was inferred, it was destructively mutated using t.assignType, but this proved to be problematic in the presence of overloads, because the bindings established while a non-matching overload is tested must be reverted/forgotten. tyInferred offers a non-destructive way to keep track of the inference progress. While introducing new types usually requires a lot of code paths in the compiler to updated, currently tyInferred is only a short-lived type within the concept body pass and it's unlikely to introduce breakage elsewhere in the compiler. --- compiler/ast.nim | 34 +++++++--- compiler/ccgtypes.nim | 2 +- compiler/ccgutils.nim | 2 +- compiler/jsgen.nim | 2 + compiler/semasgn.nim | 2 +- compiler/semexprs.nim | 1 - compiler/sigmatch.nim | 151 ++++++++++++++++++++++-------------------- compiler/types.nim | 27 +++++--- compiler/vmdeps.nim | 1 + 9 files changed, 126 insertions(+), 96 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index 4a25e03366..f7681222c6 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -354,44 +354,52 @@ type tyUnused, tyProxy # used as errornous type (for idetools) - tyBuiltInTypeClass #\ + tyBuiltInTypeClass # Type such as the catch-all object, tuple, seq, etc - tyUserTypeClass #\ + tyUserTypeClass # the body of a user-defined type class - tyUserTypeClassInst #\ + tyUserTypeClassInst # Instance of a parametric user-defined type class. # Structured similarly to tyGenericInst. # tyGenericInst represents concrete types, while # this is still a "generic param" that will bind types # and resolves them during sigmatch and instantiation. - tyCompositeTypeClass #\ + tyCompositeTypeClass # Type such as seq[Number] # The notes for tyUserTypeClassInst apply here as well # sons[0]: the original expression used by the user. # sons[1]: fully expanded and instantiated meta type # (potentially following aliases) - tyAnd, tyOr, tyNot #\ + tyInferred + # In the initial state `base` stores a type class constraining + # the types that can be inferred. After a candidate type is + # selected, it's stored in `lastSon`. Between `base` and `lastSon` + # there may be 0, 2 or more types that were also considered as + # possible candidates in the inference process (i.e. lastSon will + # be updated to store a type best conforming to all candidates) + + tyAnd, tyOr, tyNot # boolean type classes such as `string|int`,`not seq`, # `Sortable and Enumable`, etc - tyAnything #\ + tyAnything # a type class matching any type - tyStatic #\ + tyStatic # a value known at compile type (the underlying type is .base) - tyFromExpr #\ + tyFromExpr # This is a type representing an expression that depends # on generic parameters (the expression is stored in t.n) # It will be converted to a real type only during generic # instantiation and prior to this it has the potential to # be any type. - tyFieldAccessor #\ + tyFieldAccessor # Expressions such as Type.field (valid in contexts such # as the `is` operator and magics like `high` and `low`). # Could be lifted to a single argument proc returning the @@ -400,7 +408,7 @@ type # sons[1]: field type # .n: nkDotExpr storing the field name - tyVoid #\ + tyVoid # now different from tyEmpty, hurray! static: @@ -482,7 +490,6 @@ type tfHasStatic tfGenericTypeParam tfImplicitTypeParam - tfInferrableTypeClassTypeParam tfWildcard # consider a proc like foo[T, I](x: Type[T, I]) # T and I here can bind to both typedesc and static types # before this is determined, we'll consider them to be a @@ -1037,6 +1044,9 @@ proc newStrNode*(kind: TNodeKind, strVal: string): PNode = result = newNode(kind) result.strVal = strVal +template previouslyInferred*(t: PType): PType = + if t.sons.len > 1: t.lastSon else: nil + proc newSym*(symKind: TSymKind, name: PIdent, owner: PSym, info: TLineInfo): PSym = # generates a symbol and initializes the hash field too @@ -1279,6 +1289,8 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType = when debugIds: registerId(result) result.sym = t.sym # backend-info should not be copied +proc exactReplica*(t: PType): PType = copyType(t, t.owner, true) + proc copySym*(s: PSym, keepId: bool = false): PSym = result = newSym(s.kind, s.name, s.owner, s.info) #result.ast = nil # BUGFIX; was: s.ast which made problems diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 8fdd97428c..0bbb6e4145 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -165,7 +165,7 @@ proc mapType(typ: PType): TCTypeKind = of tyOpenArray, tyArray, tyVarargs: result = ctArray of tyObject, tyTuple: result = ctStruct of tyGenericBody, tyGenericInst, tyGenericParam, tyDistinct, tyOrdinal, - tyTypeDesc, tyAlias: + tyTypeDesc, tyAlias, tyInferred: result = mapType(lastSon(typ)) of tyEnum: if firstOrd(typ) < 0: diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index afcf24167c..c37a8fcdba 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -110,7 +110,7 @@ proc getUniqueType*(key: PType): PType = of tyDistinct: if key.deepCopy != nil: result = key else: result = getUniqueType(lastSon(key)) - of tyGenericInst, tyOrdinal, tyStatic, tyAlias: + of tyGenericInst, tyOrdinal, tyStatic, tyAlias, tyInferred: result = getUniqueType(lastSon(key)) #let obj = lastSon(key) #if obj.sym != nil and obj.sym.name.s == "TOption": diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index f5e8027b3e..ff467c986b 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -167,6 +167,8 @@ proc mapType(typ: PType): TJSTypeKind = tyNone, tyFromExpr, tyForward, tyEmpty, tyFieldAccessor, tyExpr, tyStmt, tyTypeDesc, tyTypeClasses, tyVoid, tyAlias: result = etyNone + of tyInferred: + result = mapType(typ.lastSon) of tyStatic: if t.n != nil: result = mapType(lastSon t) else: result = etyNone diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim index 70765d0878..f2144037c6 100644 --- a/compiler/semasgn.nim +++ b/compiler/semasgn.nim @@ -226,7 +226,7 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = tyGenericParam, tyGenericBody, tyNil, tyExpr, tyStmt, tyTypeDesc, tyGenericInvocation, tyForward: internalError(c.info, "assignment requested for type: " & typeToString(t)) - of tyOrdinal, tyRange, + of tyOrdinal, tyRange, tyInferred, tyGenericInst, tyFieldAccessor, tyStatic, tyVar, tyAlias: liftBodyAux(c, lastSon(t), body, x, y) of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("liftBodyAux") diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index d81abe20ee..74c4ae5d5d 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -323,7 +323,6 @@ proc isOpImpl(c: PContext, n: PNode): PNode = maybeLiftType(t2, c, n.info) var m: TCandidate initCandidate(c, m, t2) - discard inferTypeClassParam(m, t1, rhsOrigType) let match = typeRel(m, t2, t1) >= isSubtype # isNone result = newIntNode(nkIntLit, ord(match)) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 505e785d6b..0dee70139d 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -49,6 +49,10 @@ type # a distrinct type typedescMatched*: bool isNoCall*: bool # misused for generic type instantiations C[T] + inferredTypes: seq[PType] # inferred types during the current signature + # matching. they will be reset if the matching + # is not successful. may replace the bindings + # table in the future. mutabilityProblem*: uint8 # tyVar mismatch inheritancePenalty: int # to prefer closest father object type errors*: CandidateErrors # additional clarifications to be displayed to the @@ -600,17 +604,17 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate, case typ.kind of tyStatic: param = paramSym skConst - param.typ = typ.base + param.typ = typ.exactReplica param.ast = typ.n of tyUnknown: param = paramSym skVar - param.typ = typ + param.typ = typ.exactReplica else: param = paramSym skType - param.typ = makeTypeDesc(c, typ) - - if typ.isMetaType: - param.typ.flags.incl tfInferrableTypeClassTypeParam + param.typ = if typ.isMetaType: + c.newTypeWithSons(tyInferred, @[typ]) + else: + makeTypeDesc(c, typ) addDecl(c, param) typeParams.safeAdd((param, typ)) @@ -718,8 +722,51 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = assert(aOrig != nil) + var + useTypeLoweringRuleInTypeClass = c.c.inTypeClass > 0 and + not c.isNoCall and + f.kind != tyTypeDesc + + aOrig = if useTypeLoweringRuleInTypeClass: + aOrig.skipTypes({tyTypeDesc, tyFieldAccessor}) + else: + aOrig + + if aOrig.kind == tyInferred: + # echo "INFER A" + # debug f + # debug aOrig + let prev = aOrig.previouslyInferred + if prev != nil: + return typeRel(c, f, prev) + else: + var candidate = f + + case f.kind + of tyGenericParam: + var prev = PType(idTableGet(c.bindings, f)) + if prev != nil: candidate = prev + of tyFromExpr: + let computedType = tryResolvingStaticExpr(c, f.n).typ + case computedType.kind + of tyTypeDesc: + candidate = computedType.base + of tyStatic: + candidate = computedType + else: + localError(f.n.info, errTypeExpected) + else: + discard + + result = typeRel(c, aOrig.base, candidate) + if result != isNone: + c.inferredTypes.safeAdd aOrig + aOrig.sons.add candidate + result = isEqual + return + # var and static arguments match regular modifier-free types - let a = aOrig.skipTypes({tyStatic, tyVar}).maybeSkipDistinct(c.calleeSym) + var a = aOrig.skipTypes({tyStatic, tyVar}).maybeSkipDistinct(c.calleeSym) # XXX: Theoretically, maybeSkipDistinct could be called before we even # start the param matching process. This could be done in `prepareOperand` # for example, but unfortunately `prepareOperand` is not called in certain @@ -1258,6 +1305,20 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = # XXX endless recursion? #result = typeRel(c, prev, aOrig) result = isNone + + of tyInferred: + # echo "INFER F" + # debug f + # debug a + let prev = f.previouslyInferred + if prev != nil: + result = typeRel(c, prev, a) + else: + result = typeRel(c, f.base, a) + if result != isNone: + c.inferredTypes.safeAdd f + f.sons.add a + of tyTypeDesc: var prev = PType(idTableGet(c.bindings, f)) if prev == nil: @@ -1401,59 +1462,14 @@ proc incMatches(m: var TCandidate; r: TTypeRelation; convMatch = 1) = of isEqual: inc(m.exactMatches) of isNone: discard -proc skipToInferrableParam(tt: PType): PType = - var t = tt - while t != nil: - if tfInferrableTypeClassTypeParam in t.flags: - return t - if t.sonsLen > 0 and t.kind == tyTypeDesc: - t = t.base - else: - return nil - - return nil - -proc inferTypeClassParam*(m: var TCandidate, f, a: PType): bool = - var c = m.c - if c.inTypeClass == 0: return false - - var inferrableType = a.skipToInferrableParam - if inferrableType == nil: return false - - var inferAs = f - - case f.kind - of tyGenericParam: - var prev = PType(idTableGet(m.bindings, f)) - if prev != nil: inferAs = prev - - of tyFromExpr: - let computedType = tryResolvingStaticExpr(m, f.n).typ - case computedType.kind - of tyTypeDesc: - inferAs = computedType.base - of tyStatic: - inferAs = computedType - else: - localError(f.n.info, errTypeExpected) - - else: - discard - - inferrableType.assignType inferAs - return true - -proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, +proc paramTypesMatchAux(m: var TCandidate, f, a: PType, argSemantized, argOrig: PNode): PNode = var fMaybeStatic = f.skipTypes({tyDistinct}) arg = argSemantized - argType = argType + a = a c = m.c - if inferTypeClassParam(m, f, argType): - return argSemantized - if tfHasStatic in fMaybeStatic.flags: # XXX: When implicit statics are the default # this will be done earlier - we just have to @@ -1461,12 +1477,12 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, # XXX: weaken tyGenericParam and call it tyGenericPlaceholder # and finally start using tyTypedesc for generic types properly. - if argType.kind == tyGenericParam and tfWildcard in argType.flags: - argType.assignType(f) - # put(m.bindings, f, argType) + if a.kind == tyGenericParam and tfWildcard in a.flags: + a.assignType(f) + # put(m.bindings, f, a) return argSemantized - if argType.kind == tyStatic: + if a.kind == tyStatic: if m.callee.kind == tyGenericBody and argType.n == nil and tfGenericTypeParam notin argType.flags: @@ -1477,20 +1493,9 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, arg.typ = newTypeS(tyStatic, c) arg.typ.sons = @[evaluated.typ] arg.typ.n = evaluated - argType = arg.typ + a = arg.typ - var - useTypeLoweringRuleInTypeClass = argType != nil and - c.inTypeClass > 0 and - not m.isNoCall and - f.kind != tyTypeDesc - - a = if useTypeLoweringRuleInTypeClass: - argType.skipTypes({tyTypeDesc, tyFieldAccessor}) - else: - argType - - r = typeRel(m, f, a) + var r = typeRel(m, f, a) if r != isNone and m.calleeSym != nil and m.calleeSym.kind in {skMacro, skTemplate}: @@ -1931,6 +1936,10 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) = def = implicitConv(nkHiddenStdConv, formal.typ, def, m, c) setSon(m.call, formal.position + 1, def) inc(f) + # forget all inferred types if the overload matching failed + if m.state == csNoMatch: + for t in m.inferredTypes: + if t.sonsLen > 1: t.sons.setLen 1 proc argtypeMatches*(c: PContext, f, a: PType): bool = var m: TCandidate diff --git a/compiler/types.nim b/compiler/types.nim index 285854aa0c..3e124412be 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -55,16 +55,17 @@ const # TODO: Remove tyTypeDesc from each abstractX and (where necessary) # replace with typedescX abstractPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyDistinct, tyOrdinal, - tyTypeDesc, tyAlias} + tyTypeDesc, tyAlias, tyInferred} abstractVar* = {tyVar, tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, - tyAlias} + tyAlias, tyInferred} abstractRange* = {tyGenericInst, tyRange, tyDistinct, tyOrdinal, tyTypeDesc, - tyAlias} + tyAlias, tyInferred} abstractVarRange* = {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal, - tyTypeDesc, tyAlias} - abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias} - - skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias} + tyTypeDesc, tyAlias, tyInferred} + abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, + tyInferred} + skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias, + tyInferred} # typedescX is used if we're sure tyTypeDesc should be included (or skipped) typedescPtrs* = abstractPtrs + {tyTypeDesc} typedescInst* = abstractInst + {tyTypeDesc} @@ -410,7 +411,7 @@ const "unused0", "unused1", "unused2", "varargs[$1]", "unused", "Error Type", "BuiltInTypeClass", "UserTypeClass", - "UserTypeClassInst", "CompositeTypeClass", + "UserTypeClassInst", "CompositeTypeClass", "inferred", "and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor", "void"] @@ -476,6 +477,10 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = of tyTuple: "tuple" of tyOpenArray: "openarray" else: typeToStr[t.base.kind] + of tyInferred: + let concrete = t.previouslyInferred + if concrete != nil: result = typeToString(concrete) + else: result = "inferred[" & typeToString(t.base) & "]" of tyUserTypeClassInst: let body = t.base result = body.sym.name.s & "[" @@ -971,7 +976,9 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool = result = sameTypeOrNilAux(a.sons[0], b.sons[0], c) and sameValue(a.n.sons[0], b.n.sons[0]) and sameValue(a.n.sons[1], b.n.sons[1]) - of tyGenericInst, tyAlias: discard + of tyGenericInst, tyAlias, tyInferred: + cycleCheck() + result = sameTypeAux(a.lastSon, b.lastSon, c) of tyNone: result = false of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("sameFlags") @@ -1118,7 +1125,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, result = nil of tyOrdinal: if kind != skParam: result = t - of tyGenericInst, tyDistinct, tyAlias: + of tyGenericInst, tyDistinct, tyAlias, tyInferred: result = typeAllowedAux(marker, lastSon(t), kind, flags) of tyRange: if skipTypes(t.sons[0], abstractInst-{tyTypeDesc}).kind notin diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index d684c4c32b..7094d174b0 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -294,6 +294,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; of tyOr: result = mapTypeToBracket("or", mOr, t, info) of tyNot: result = mapTypeToBracket("not", mNot, t, info) of tyAnything: result = atomicType("anything", mNone) + of tyInferred: internalAssert false of tyStatic, tyFromExpr, tyFieldAccessor: if inst: if t.n != nil: result = t.n.copyTree