Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions compiler/semcall.nim
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,13 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
addTypeBoundSymbols(c.graph, arg.typ, name, filter, symMarker, syms)

if z.state == csMatch:
# little hack so that iterators are preferred over everything else:
# Iterator preference is heuristic in iterator-admitting contexts.
# The dedicated iterable path uses `iteratorPreference`, other
# context use exact-match bump
if sym.kind == skIterator:
if not (efWantIterator notin flags and efWantIterable in flags):
if efPreferIteratorForIterable in flags:
inc(z.iteratorPreference)
elif not (efWantIterator notin flags and efWantIterable in flags):
inc(z.exactMatches, 200)
else:
dec(z.exactMatches, 200)
Expand Down Expand Up @@ -671,7 +675,7 @@ proc bracketNotFoundError(c: PContext; n: PNode; flags: TExprFlags) =
# copied from semOverloadedCallAnalyzeEffects, might be overkill:
const baseFilter = {skProc, skFunc, skMethod, skConverter, skMacro, skTemplate}
let filter =
if flags*{efInTypeof, efWantIterator, efWantIterable} != {}:
if flags*{efInTypeof, efWantIterator, efWantIterable, efPreferIteratorForIterable} != {}:
baseFilter + {skIterator}
else: baseFilter
# this will add the errors:
Expand Down
13 changes: 12 additions & 1 deletion compiler/semdata.nim
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,18 @@ type
inst*: PInstantiation

TExprFlag* = enum
efLValue, efWantIterator, efWantIterable, efInTypeof,
efLValue,
# The expression is used as an assignable location.
efWantIterator,
# Admit iterator candidates and prefer them during overload resolution.
efWantIterable,
# Admit iterator candidates for expressions that may feed iterable-style
# chaining.
efPreferIteratorForIterable,
# Prefer iterator candidates for `iterable[T]` matching and wrap a
# successful iterator call as `tyIterable`.
efInTypeof,
# The expression is being semchecked under `typeof`.
efNeedStatic,
# Use this in contexts where a static value is mandatory
efPreferStatic,
Expand Down
7 changes: 4 additions & 3 deletions compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ proc semStaticExpr(c: PContext, n: PNode; expectedType: PType = nil): PNode =

proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
flags: TExprFlags; expectedType: PType = nil): PNode =
if flags*{efInTypeof, efWantIterator, efWantIterable} != {}:
if flags*{efInTypeof, efWantIterator, efWantIterable, efPreferIteratorForIterable} != {}:
# consider: 'for x in pReturningArray()' --> we don't want the restriction
# to 'skIterator' anymore; skIterator is preferred in sigmatch already
# for typeof support.
Expand All @@ -1006,7 +1006,8 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
# See bug #2051:
result[0] = newSymNode(errorSym(c, n))
elif callee.kind == skIterator:
if efWantIterable in flags:
if result.typ.kind != tyIterable and
flags * {efWantIterable, efPreferIteratorForIterable} != {}:
let typ = newTypeS(tyIterable, c)
rawAddSon(typ, result.typ)
result.typ = typ
Expand Down Expand Up @@ -1525,7 +1526,7 @@ proc builtinFieldAccess(c: PContext; n: PNode; flags: var TExprFlags): PNode =
return

# extra flags since LHS may become a call operand:
n[0] = semExprWithType(c, n[0], flags+{efDetermineType, efWantIterable, efAllowSymChoice})
n[0] = semExprWithType(c, n[0], flags + {efDetermineType, efWantIterable, efAllowSymChoice})
#restoreOldStyleType(n[0])
var i = considerQuotedIdent(c, n[1], n)
var ty = n[0].typ
Expand Down
27 changes: 17 additions & 10 deletions compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1096,7 +1096,12 @@ proc symForVar(c: PContext, n: PNode): PSym =
proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
result = n
let iterBase = n[^2].typ
var iter = skipTypes(iterBase, {tyGenericInst, tyAlias, tySink, tyOwned})
let iterType =
if iterBase.kind == tyIterable:
iterBase.skipModifier
else:
skipTypes(iterBase, {tyAlias, tySink, tyOwned})
var iter = iterType
var iterAfterVarLent = iter.skipTypes({tyGenericInst, tyAlias, tyLent, tyVar})
# n.len == 3 means that there is one for loop variable
# and thus no tuple unpacking:
Expand Down Expand Up @@ -1129,10 +1134,9 @@ proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
else:
var v = symForVar(c, n[0])
if getCurrOwner(c).kind == skModule: incl(v, sfGlobal)
# BUGFIX: don't use `iter` here as that would strip away
# the ``tyGenericInst``! See ``tests/compile/tgeneric.nim``
# for an example:
v.typ = iterBase
# Use `iterType` here: it removes outer `tyIterable` / alias-like wrappers
# from the loop source, but still preserves `tyGenericInst` for the loop var.
v.typ = iterType
n[0] = newSymNode(v)
if sfGenSym notin v.flags and not isDiscardUnderscore(v): addDecl(c, v)
elif v.owner == nil: setOwner(v, getCurrOwner(c))
Expand Down Expand Up @@ -1196,14 +1200,14 @@ proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
c.p.breakInLoop = oldBreakInLoop
dec(c.p.nestedLoopCounter)

proc implicitIterator(c: PContext, it: string, arg: PNode): PNode =
proc implicitIterator(c: PContext, it: string, arg: PNode, flags: TExprFlags): PNode =
result = newNodeI(nkCall, arg.info)
result.add(newIdentNode(getIdent(c.cache, it), arg.info))
if arg.typ != nil and arg.typ.kind in {tyVar, tyLent}:
result.add newDeref(arg)
else:
result.add arg
result = semExprNoDeref(c, result, {efWantIterator})
result = semExprNoDeref(c, result, flags + {efWantIterator})

proc isTrivalStmtExpr(n: PNode): bool =
for i in 0..<n.len-1:
Expand Down Expand Up @@ -1289,7 +1293,8 @@ proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode =
if result != nil: return result
openScope(c)
result = n
n[^2] = semExprNoDeref(c, n[^2], {efWantIterator})
let iteratorFlags = flags * {efPreferIteratorForIterable}
n[^2] = semExprNoDeref(c, n[^2], iteratorFlags + {efWantIterator})
var call = n[^2]

if call.kind == nkStmtListExpr and (isTrivalStmtExpr(call) or (call.lastSon.kind in nkCallKinds and call.lastSon[0].sym.kind == skIterator)):
Expand All @@ -1309,14 +1314,16 @@ proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode =
elif not isCallExpr or call[0].kind != nkSym or
call[0].sym.kind != skIterator:
if n.len == 3:
n[^2] = implicitIterator(c, "items", n[^2])
n[^2] = implicitIterator(c, "items", n[^2], iteratorFlags)
elif n.len == 4:
n[^2] = implicitIterator(c, "pairs", n[^2])
n[^2] = implicitIterator(c, "pairs", n[^2], iteratorFlags)
else:
localError(c.config, n[^2].info, "iterator within for loop context expected")
result = semForVars(c, n, flags)
else:
result = semForVars(c, n, flags)
if n[^2].typ != nil and n[^2].typ.kind == tyIterable:
n[^2].typ = n[^2].typ.skipModifier
# propagate any enforced VoidContext:
if n[^1].typ == c.enforceVoidContext:
result.typ = c.enforceVoidContext
Expand Down
26 changes: 23 additions & 3 deletions compiler/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ type

TCandidate* = object
c*: PContext
exactMatches*: int # also misused to prefer iters over procs
exactMatches*: int
iteratorPreference*: int # prefer iterators in iterator-oriented contexts
genericMatches: int # also misused to prefer constraints
subtypeMatches: int
intConvMatches: int # conversions to int are not as expensive
Expand Down Expand Up @@ -110,7 +111,8 @@ proc markOwnerModuleAsUsed*(c: PContext; s: PSym)
proc initCandidateAux(ctx: PContext,
callee: PType): TCandidate {.inline.} =
result = TCandidate(c: ctx, exactMatches: 0, subtypeMatches: 0,
convMatches: 0, intConvMatches: 0, genericMatches: 0,
iteratorPreference: 0, convMatches: 0, intConvMatches: 0,
genericMatches: 0,
state: csEmpty, firstMismatch: MismatchInfo(),
callee: callee, call: nil, baseTypeMatch: false,
genericConverter: false, inheritancePenalty: -1
Expand Down Expand Up @@ -393,6 +395,7 @@ proc complexDisambiguation(a, b: PType): int =
proc writeMatches*(c: TCandidate) =
echo "Candidate '", c.calleeSym.name.s, "' at ", c.c.config $ c.calleeSym.info
echo " exact matches: ", c.exactMatches
echo " iterator preference: ", c.iteratorPreference
echo " generic matches: ", c.genericMatches
echo " subtype matches: ", c.subtypeMatches
echo " intconv matches: ", c.intConvMatches
Expand All @@ -411,6 +414,8 @@ proc cmpInheritancePenalty(a, b: int): int =
proc cmpCandidates*(a, b: TCandidate, isFormal=true): int =
result = a.exactMatches - b.exactMatches
if result != 0: return
result = a.iteratorPreference - b.iteratorPreference
if result != 0: return
result = a.genericMatches - b.genericMatches
if result != 0: return
result = a.subtypeMatches - b.subtypeMatches
Expand Down Expand Up @@ -2748,7 +2753,8 @@ proc prepareOperand(c: PContext; formal: PType; a: PNode, newlyTyped: var bool):
result = a
elif a.typ.isNil:
if formal.kind == tyIterable:
let flags = {efDetermineType, efAllowStmt, efWantIterator, efWantIterable}
let flags = {efDetermineType, efAllowStmt, efWantIterator, efWantIterable,
efPreferIteratorForIterable}
result = c.semOperand(c, a, flags)
else:
# XXX This is unsound! 'formal' can differ from overloaded routine to
Expand All @@ -2765,6 +2771,20 @@ proc prepareOperand(c: PContext; formal: PType; a: PNode, newlyTyped: var bool):
considerGenSyms(c, result)
if result.kind != nkHiddenDeref and result.typ.kind in {tyVar, tyLent} and c.matchedConcept == nil:
result = newDeref(result)
# Recovery for calls resolved too early as non-iterators.
# TODO: retry only skIterator overloads instead of re-semming,
# or preserve iterator-candidates info from the earlier semcheck.
if formal.kind == tyIterable and result.typ.kind != tyIterable and
a.kind in nkCallKinds and a[0].kind in {nkIdent, nkAccQuoted, nkSym, nkOpenSym}:
let recheck = copyTree(a)
recheck.typ = nil
if recheck[0].kind == nkSym and recheck[0].sym != nil:
recheck[0] = newIdentNode(recheck[0].sym.name, recheck[0].info)
let flags = {efDetermineType, efAllowStmt, efNoUndeclared,
efWantIterator, efWantIterable, efPreferIteratorForIterable}
let fresh = c.semOperand(c, recheck, flags)
if fresh.typ != nil and fresh.typ.kind == tyIterable:
return fresh

proc prepareOperand(c: PContext; a: PNode, newlyTyped: var bool): PNode =
if a.typ.isNil:
Expand Down
44 changes: 25 additions & 19 deletions doc/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -2628,10 +2628,10 @@ Overload resolution
In a call `p(args)` where `p` may refer to more than one
candidate, it is said to be a symbol choice. Overload resolution will attempt to
find the best candidate, thus transforming the symbol choice into a resolved symbol.
The routine `p` that matches best is selected following a series of trials explained below.
The routine `p` that matches best is selected following a series of trials explained below.
In order: Category matching, Hierarchical Order Comparison, and finally, Complexity Analysis.

If multiple candidates match equally well after all trials have been tested, the ambiguity
If multiple candidates match equally well after all trials have been tested, the ambiguity
is reported during semantic analysis.

First Trial: Category matching
Expand Down Expand Up @@ -2664,7 +2664,7 @@ resolved symbol.
For example, if a candidate with one exact match is compared to a candidate with multiple
generic matches and zero exact matches, the candidate with an exact match will win.

Below is a pseudocode interpretation of category matching, `count(p, m)` counts the number
Below is a pseudocode interpretation of category matching, `count(p, m)` counts the number
of matches of the matching category `m` for the routine `p`.

A routine `p` matches better than a routine `q` if the following
Expand Down Expand Up @@ -2692,11 +2692,11 @@ type A[T] = object
```

Matching formals for this type include `T`, `object`, `A`, `A[...]` and `A[C]` where `C` is a concrete type, `A[...]`
is a generic typeclass composition and `T` is an unconstrained generic type variable. This list is in order of
is a generic typeclass composition and `T` is an unconstrained generic type variable. This list is in order of
specificity with respect to `A` as each subsequent category narrows the set of types that are members of their match set.

In this trial, the formal parameters of candidates are compared in order (1st parameter, 2nd parameter, etc.) to search for
a candidate that has an unrivaled specificity. If such a formal parameter is found, the candidate it belongs to is chosen
a candidate that has an unrivaled specificity. If such a formal parameter is found, the candidate it belongs to is chosen
as the resolved symbol.

Third Trial: Complexity Analysis
Expand Down Expand Up @@ -2951,13 +2951,13 @@ proc sort*[I: Index; T: Comparable](x: var Indexable[I, T])

In the above example, `Comparable` and `Indexable` are types that will match any type that
can can bind each definition declared in the concept body. The special `Self` type defined
in the concept body refers to the type being matched, also called the "implementation" of
the concept. Implementations that match the concept are generic matches, and the concept
in the concept body refers to the type being matched, also called the "implementation" of
the concept. Implementations that match the concept are generic matches, and the concept
typeclasses themselves work in a similar way to generic type variables in that they are never
concrete types themselves (even if they have concrete type parameters such as `Indexable[int, int]`)
and expressions like `typeof(x)` in the body of `proc sort` from the above example will return the
and expressions like `typeof(x)` in the body of `proc sort` from the above example will return the
type of the implementation, not the concept typeclass. Concepts are useful for providing information
to the compiler in generic contexts, most notably for generic type checking, and as a tool for
to the compiler in generic contexts, most notably for generic type checking, and as a tool for
[Overload resolution]. Generic type checking is forthcoming, so this will only explain overload
resolution for now.

Expand All @@ -2984,7 +2984,7 @@ Concept overload resolution

When an operand's type is being matched to a concept, the operand's type is set as the "potential
implementation". For each definition in the concept body, overload resolution is performed by substituting `Self`
for the potential implementation to try and find a match for each definition. If this succeeds, the concept
for the potential implementation to try and find a match for each definition. If this succeeds, the concept
matches. Implementations do not need to exactly match the definitions in the concept. For example:

```nim
Expand All @@ -3008,7 +3008,7 @@ This leads to confusing and impractical behavior in most situations, so the rule
1. if a concept is being compared with `T` or any type that accepts all other types (`auto`) the concept
is more specific
2. if the concept is being compared with another concept the result is deferred to [Concept subset matching]
3. in any other case the concept is less specific then it's competitor
3. in any other case the concept is less specific then it's competitor

Currently, the concept evaluation mechanism evaluates to a successful match on the first acceptable candidate
for each defined binding. This has a couple of notable effects:
Expand Down Expand Up @@ -4610,10 +4610,10 @@ for any type (with some exceptions) by defining a routine with the name `[]`.
```nim
type Foo = object
data: seq[int]

proc `[]`(foo: Foo, i: int): int =
result = foo.data[i]

let foo = Foo(data: @[1, 2, 3])
echo foo[1] # 2
```
Expand All @@ -4624,12 +4624,12 @@ which has precedence over assigning to the result of `[]`.
```nim
type Foo = object
data: seq[int]

proc `[]`(foo: Foo, i: int): int =
result = foo.data[i]
proc `[]=`(foo: var Foo, i: int, val: int) =
foo.data[i] = val

var foo = Foo(data: @[1, 2, 3])
echo foo[1] # 2
foo[1] = 5
Expand Down Expand Up @@ -4861,7 +4861,14 @@ default to being inline, but this may change in future versions of the
implementation.

The `iterator` type is always of the calling convention `closure`
implicitly; the following example shows how to use iterators to implement
implicitly.

Unlike named iterators, anonymous iterator expressions evaluate
to the `iterator` type. In practice, this means a named iterator declaration
without `{.closure.}` defaults to inline, but an expression like `let it =
iterator(): int = yield 1` produces a callable closure iterator value.

The following example shows how to use iterators to implement
a `collaborative tasking`:idx: system:

```nim
Expand Down Expand Up @@ -6401,7 +6408,7 @@ The default for symbols of entity `type`, `var`, `let` and `const`
is `gensym`. For `proc`, `iterator`, `converter`, `template`,
`macro`, the default is `inject`, but if a `gensym` symbol with the same name
is defined in the same syntax-level scope, it will be `gensym` by default.
This can be overridden by marking the routine as `inject`.
This can be overridden by marking the routine as `inject`.

If the name of the entity is passed as a template parameter, it is an `inject`'ed symbol:

Expand Down Expand Up @@ -7242,7 +7249,7 @@ identifier is considered ambiguous, which can be resolved in the following ways:

write(stdout, x) # error: x is ambiguous
write(stdout, A.x) # no error: qualifier used

proc bar(a: int): int = a + 1
assert bar(x) == x + 1 # no error: only A.x of type int matches

Expand Down Expand Up @@ -9324,4 +9331,3 @@ It is not valid to pass an lvalue of a supertype to an `out T` parameter:

However, in the future this could be allowed and provide a better way to write object
constructors that take inheritance into account.

6 changes: 6 additions & 0 deletions tests/iter/tinlineitervalue.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
discard """
action: reject
errormsg: "attempting to call routine: 'items'"
"""

let chars = "abc".items()
Loading