summary refs log tree commit diff stats
path: root/lib/js/jsffi.nim
blob: f34efe9a299f9e095beb1efd907d60faa1eefa61 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
#
#
#            Nim's Runtime Library
#        (c) Copyright 2017 Nim Authors
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This Module implements types and macros to facilitate the wrapping of, and
## interaction with JavaScript libraries. Using the provided types ``JsObject``
## and ``JsAssoc`` together with the provided macros allows for smoother
## interfacing with JavaScript, allowing for example quick and easy imports of
## JavaScript variables:
##
## .. code-block:: nim
##
##  # Here, we are using jQuery for just a few calls and do not want to wrap the
##  # whole library:
##
##  # import the document object and the console
##  var document {. importc, nodecl .}: JsObject
##  var console {. importc, nodecl .}: JsObject
##  # import the "$" function
##  proc jq(selector: JsObject): JsObject {. importcpp: "$(#)" .}
##
##  # Use jQuery to make the following code run, after the document is ready.
##  # This uses an experimental ``.()`` operator for ``JsObject``, to emit
##  # JavaScript calls, when no corresponding proc exists for ``JsObject``.
##  proc main =
##    jq(document).ready(proc() =
##      console.log("Hello JavaScript!")
##    )
##

when not defined(js) and not defined(nimdoc) and not defined(nimsuggest):
  {.fatal: "Module jsFFI is designed to be used with the JavaScript backend.".}

import macros, tables

const
  setImpl = "#[#] = #"
  getImpl = "#[#]"

var
  mangledNames {. compileTime .} = initTable[string, string]()
  nameCounter {. compileTime .} = 0

proc validJsName(name: string): bool =
  result = true
  const reservedWords = ["break", "case", "catch", "class", "const", "continue",
    "debugger", "default", "delete", "do", "else", "export", "extends",
    "finally", "for", "function", "if", "import", "in", "instanceof", "new",
    "return", "super", "switch", "this", "throw", "try", "typeof", "var",
    "void", "while", "with", "yield", "enum", "implements", "interface",
    "let", "package", "private", "protected", "public", "static", "await",
    "abstract", "boolean", "byte", "char", "double", "final", "float", "goto",
    "int", "long", "native", "short", "synchronized", "throws", "transient",
    "volatile", "null", "true", "false"]
  case name
  of reservedWords: return false
  else: discard
  if name[0] notin {'A'..'Z','a'..'z','_','$'}: return false
  for chr in name:
    if chr notin {'A'..'Z','a'..'z','_','$','0'..'9'}:
      return false

template mangleJsName(name: cstring): cstring =
  inc nameCounter
  "mangledName" & $nameCounter

type
  JsRoot* = ref object of RootObj
    ## Root type of both JsObject and JsAssoc
  JsObject* = ref object of JsRoot
    ## Dynamically typed wrapper around a JavaScript object.
  JsAssoc*[K, V] = ref object of JsRoot
    ## Statically typed wrapper around a JavaScript object.
  NotString = concept c
    c isnot string
  js* = JsObject

var jsarguments* {.importc: "arguments", nodecl}: JsObject
  ## JavaScript's arguments pseudo-variable

# New
proc newJsObject*: JsObject {. importcpp: "{@}" .}
  ## Creates a new empty JsObject
proc newJsAssoc*[K, V]: JsAssoc[K, V] {. importcpp: "{@}" .}
  ## Creates a new empty JsAssoc with key type `K` and value type `V`.

# Checks
proc hasOwnProperty*(x: JsObject, prop: cstring): bool
  {. importcpp: "#.hasOwnProperty(#)" .}
  ## Checks, whether `x` has a property of name `prop`.

proc jsTypeOf*(x: JsObject): cstring {. importcpp: "typeof(#)" .}
  ## Returns the name of the JsObject's JavaScript type as a cstring.

proc jsnew*(x: auto): JsObject {.importcpp: "(new #)".}
  ## Turns a regular function call into an invocation of the
  ## JavaScript's `new` operator

proc jsdelete*(x: auto): JsObject {.importcpp: "(delete #)".}
  ## JavaScript's `delete` operator

# Conversion to and from JsObject
proc to*(x: JsObject, T: typedesc): T {. importcpp: "(#)" .}
  ## Converts a JsObject `x` to type `T`.

proc toJs*[T](val: T): JsObject {. importcpp: "(#)" .}
  ## Converts a value of any type to type JsObject

template toJs*(s: string): JsObject = cstring(s).toJs

macro jsFromAst*(n: untyped): untyped =
  result = n
  if n.kind == nnkStmtList:
    result = newProc(procType = nnkDo, body = result)
  return quote: toJs(`result`)

proc `&`*(a, b: cstring): cstring {.importcpp: "(# + #)".}
  ## Concatenation operator for JavaScript strings

proc `+`  *(x, y: JsObject): JsObject {. importcpp: "(# + #)" .}
proc `-`  *(x, y: JsObject): JsObject {. importcpp: "(# - #)" .}
proc `*`  *(x, y: JsObject): JsObject {. importcpp: "(# * #)" .}
proc `/`  *(x, y: JsObject): JsObject {. importcpp: "(# / #)" .}
proc `%`  *(x, y: JsObject): JsObject {. importcpp: "(# % #)" .}
proc `+=` *(x, y: JsObject): JsObject {. importcpp: "(# += #)", discardable .}
proc `-=` *(x, y: JsObject): JsObject {. importcpp: "(# -= #)", discardable .}
proc `*=` *(x, y: JsObject): JsObject {. importcpp: "(# *= #)", discardable .}
proc `/=` *(x, y: JsObject): JsObject {. importcpp: "(# /= #)", discardable .}
proc `%=` *(x, y: JsObject): JsObject {. importcpp: "(# %= #)", discardable .}
proc `++` *(x: JsObject): JsObject    {. importcpp: "(++#)" .}
proc `--` *(x: JsObject): JsObject    {. importcpp: "(--#)" .}
proc `>`  *(x, y: JsObject): JsObject {. importcpp: "(# > #)" .}
proc `<`  *(x, y: JsObject): JsObject {. importcpp: "(# < #)" .}
proc `>=` *(x, y: JsObject): JsObject {. importcpp: "(# >= #)" .}
proc `<=` *(x, y: JsObject): JsObject {. importcpp: "(# <= #)" .}
proc `and`*(x, y: JsObject): JsObject {. importcpp: "(# && #)" .}
proc `or` *(x, y: JsObject): JsObject {. importcpp: "(# || #)" .}
proc `not`*(x: JsObject): JsObject    {. importcpp: "(!#)" .}
proc `in` *(x, y: JsObject): JsObject {. importcpp: "(# in #)" .}

proc `[]`*(obj: JsObject, field: cstring): JsObject {. importcpp: getImpl .}
  ## Return the value of a property of name `field` from a JsObject `obj`.

proc `[]`*(obj: JsObject, field: int): JsObject {. importcpp: getImpl .}
  ## Return the value of a property of name `field` from a JsObject `obj`.

proc `[]=`*[T](obj: JsObject, field: cstring, val: T) {. importcpp: setImpl .}
  ## Set the value of a property of name `field` in a JsObject `obj` to `v`.

proc `[]=`*[T](obj: JsObject, field: int, val: T) {. importcpp: setImpl .}
  ## Set the value of a property of name `field` in a JsObject `obj` to `v`.

proc `[]`*[K: NotString, V](obj: JsAssoc[K, V], field: K): V
  {. importcpp: getImpl .}
  ## Return the value of a property of name `field` from a JsAssoc `obj`.

proc `[]`*[V](obj: JsAssoc[string, V], field: cstring): V
  {. importcpp: getImpl .}
  ## Return the value of a property of name `field` from a JsAssoc `obj`.

proc `[]=`*[K: NotString, V](obj: JsAssoc[K, V], field: K, val: V)
  {. importcpp: setImpl .}
  ## Set the value of a property of name `field` in a JsAssoc `obj` to `v`.

proc `[]=`*[V](obj: JsAssoc[string, V], field: cstring, val: V)
  {. importcpp: setImpl .}
  ## Set the value of a property of name `field` in a JsAssoc `obj` to `v`.

proc `==`*(x, y: JsRoot): bool {. importcpp: "(# === #)" .}
  ## Compare two JsObjects or JsAssocs. Be careful though, as this is comparison
  ## like in JavaScript, so if your JsObjects are in fact JavaScript Objects,
  ## and not strings or numbers, this is a *comparison of references*.

{. experimental .}
macro `.`*(obj: JsObject, field: untyped): JsObject =
  ## Experimental dot accessor (get) for type JsObject.
  ## Returns the value of a property of name `field` from a JsObject `x`.
  ##
  ## Example:
  ##
  ## .. code-block:: nim
  ##
  ##  let obj = newJsObject()
  ##  obj.a = 20
  ##  console.log(obj.a) # puts 20 onto the console.
  if validJsName($field):
    let importString = "#." & $field
    result = quote do:
      proc helper(o: JsObject): JsObject
        {. importcpp: `importString`, gensym .}
      helper(`obj`)
  else:
    if not mangledNames.hasKey($field):
      mangledNames[$field] = $mangleJsName($field)
    let importString = "#." & mangledNames[$field]
    result = quote do:
      proc helper(o: JsObject): JsObject
        {. importcpp: `importString`, gensym .}
      helper(`obj`)

macro `.=`*(obj: JsObject, field, value: untyped): untyped =
  ## Experimental dot accessor (set) for type JsObject.
  ## Sets the value of a property of name `field` in a JsObject `x` to `value`.
  if validJsName($field):
    let importString = "#." & $field & " = #"
    result = quote do:
      proc helper(o: JsObject, v: auto)
        {. importcpp: `importString`, gensym .}
      helper(`obj`, `value`)
  else:
    if not mangledNames.hasKey($field):
      mangledNames[$field] = $mangleJsName($field)
    let importString = "#." & mangledNames[$field] & " = #"
    result = quote do:
      proc helper(o: JsObject, v: auto)
        {. importcpp: `importString`, gensym .}
      helper(`obj`, `value`)

macro `.()`*(obj: JsObject,
             field: untyped,
             args: varargs[JsObject, jsFromAst]): JsObject =
  ## Experimental "method call" operator for type JsObject.
  ## Takes the name of a method of the JavaScript object (`field`) and calls
  ## it with `args` as arguments, returning a JsObject (which may be discarded,
  ## and may be `undefined`, if the method does not return anything,
  ## so be careful when using this.)
  ##
  ## Example:
  ##
  ## .. code-block:: nim
  ##
  ##  # Let's get back to the console example:
  ##  var console {. importc, nodecl .}: JsObject
  ##  let res = console.log("I return undefined!")
  ##  console.log(res) # This prints undefined, as console.log always returns
  ##                   # undefined. Thus one has to be careful, when using
  ##                   # JsObject calls.
  var importString: string
  if validJsName($field):
    importString = "#." & $field & "(@)"
  else:
    if not mangledNames.hasKey($field):
      mangledNames[$field] = $mangleJsName($field)
    importString = "#." & mangledNames[$field] & "(@)"
  result = quote:
    proc helper(o: JsObject): JsObject
      {. importcpp: `importString`, gensym, discardable .}
    helper(`obj`)
  for idx in 0 ..< args.len:
    let paramName = newIdentNode(!("param" & $idx))
    result[0][3].add newIdentDefs(paramName, newIdentNode(!"JsObject"))
    result[1].add args[idx].copyNimTree

macro `.`*[K: string | cstring, V](obj: JsAssoc[K, V],
                                   field: untyped): V =
  ## Experimental dot accessor (get) for type JsAssoc.
  ## Returns the value of a property of name `field` from a JsObject `x`.
  var importString: string
  if validJsName($field):
    importString = "#." & $field
  else:
    if not mangledNames.hasKey($field):
      mangledNames[$field] = $mangleJsName($field)
    importString = "#." & mangledNames[$field]
  result = quote do:
    proc helper(o: type(`obj`)): `obj`.V
      {. importcpp: `importString`, gensym .}
    helper(`obj`)

macro `.=`*[K: string | cstring, V](obj: JsAssoc[K, V],
                                    field: untyped,
                                    value: V): untyped =
  ## Experimental dot accessor (set) for type JsAssoc.
  ## Sets the value of a property of name `field` in a JsObject `x` to `value`.
  var importString: string
  if validJsName($field):
    importString = "#." & $field & " = #"
  else:
    if not mangledNames.hasKey($field):
      mangledNames[$field] = $mangleJsName($field)
    importString = "#." & mangledNames[$field] & " = #"
  result = quote do:
    proc helper(o: type(`obj`), v: `obj`.V)
      {. importcpp: `importString`, gensym .}
    helper(`obj`, `value`)

macro `.()`*[K: string | cstring, V: proc](obj: JsAssoc[K, V],
                                           field: untyped,
                                           args: varargs[untyped]): auto =
  ## Experimental "method call" operator for type JsAssoc.
  ## Takes the name of a method of the JavaScript object (`field`) and calls
  ## it with `args` as arguments. Here, everything is typechecked, so you do not
  ## have to worry about `undefined` return values.
  let dotOp = bindSym"."
  result = quote do:
    (`dotOp`(`obj`, `field`))()
  for elem in args:
    result.add elem

# Iterators:

iterator pairs*(obj: JsObject): (cstring, JsObject) =
  ## Yields tuples of type ``(cstring, JsObject)``, with the first entry
  ## being the `name` of a fields in the JsObject and the second being its
  ## value wrapped into a JsObject.
  var k: cstring
  var v: JsObject
  {.emit: "for (var `k` in `obj`) {".}
  {.emit: "  if (!`obj`.hasOwnProperty(`k`)) continue;".}
  {.emit: "  `v`=`obj`[`k`];".}
  yield (k, v)
  {.emit: "}".}

iterator items*(obj: JsObject): JsObject =
  ## Yields the `values` of each field in a JsObject, wrapped into a JsObject.
  var v: JsObject
  {.emit: "for (var k in `obj`) {".}
  {.emit: "  if (!`obj`.hasOwnProperty(k)) continue;".}
  {.emit: "  `v`=`obj`[k];".}
  yield v
  {.emit: "}".}

iterator keys*(obj: JsObject): cstring =
  ## Yields the `names` of each field in a JsObject.
  var k: cstring
  {.emit: "for (var `k` in `obj`) {".}
  {.emit: "  if (!`obj`.hasOwnProperty(`k`)) continue;".}
  yield k
  {.emit: "}".}

iterator pairs*[K, V](assoc: JsAssoc[K, V]): (K,V) =
  ## Yields tuples of type ``(K, V)``, with the first entry
  ## being a `key` in the JsAssoc and the second being its corresponding value.
  when K is string:
    var k: cstring
  else:
    var k: K
  var v: V
  {.emit: "for (var `k` in `assoc`) {".}
  {.emit: "  if (!`assoc`.hasOwnProperty(`k`)) continue;".}
  {.emit: "  `v`=`assoc`[`k`];".}
  when K is string:
    yield ($k, v)
  else:
    yield (k, v)
  {.emit: "}".}

iterator items*[K,V](assoc: JSAssoc[K,V]): V =
  ## Yields the `values` in a JsAssoc.
  var v: V
  {.emit: "for (var k in `assoc`) {".}
  {.emit: "  if (!`assoc`.hasOwnProperty(k)) continue;".}
  {.emit: "  `v`=`assoc`[k];".}
  yield v
  {.emit: "}".}

iterator keys*[K,V](assoc: JSAssoc[K,V]): K =
  ## Yields the `keys` in a JsAssoc.
  when K is string:
    var k: cstring
  else:
    var k: K
  {.emit: "for (var `k` in `assoc`) {".}
  {.emit: "  if (!`assoc`.hasOwnProperty(`k`)) continue;".}
  when K is string:
    yield $k
  else:
    yield k
  {.emit: "}".}

# Literal generation

macro `{}`*(typ: typedesc, xs: varargs[untyped]): auto =
  ## Takes a ``typedesc`` as its first argument, and a series of expressions of
  ## type ``key: value``, and returns a value of the specified type with each
  ## field ``key`` set to ``value``, as specified in the arguments of ``{}``.
  ##
  ## Example:
  ##
  ## .. code-block:: nim
  ##
  ##  # Let's say we have a type with a ton of fields, where some fields do not
  ##  # need to be set, and we do not want those fields to be set to ``nil``:
  ##  type
  ##    ExtremelyHugeType = ref object
  ##      a, b, c, d, e, f, g: int
  ##      h, i, j, k, l: cstring
  ##      # And even more fields ...
  ##
  ##  let obj = ExtremelyHugeType{ a: 1, k: "foo".cstring, d: 42 }
  ##
  ##  # This generates roughly the same JavaScript as:
  ##  {. emit: "var obj = {a: 1, k: "foo", d: 42};" .}
  ##
  let a = !"a"
  var body = quote do:
    var `a` {.noinit.}: `typ`
    {.emit: "`a` = {};".}
  for x in xs.children:
    if x.kind == nnkExprColonExpr:
      let
        k = x[0]
        kString = quote do:
          when compiles($`k`): $`k` else: "invalid"
        v = x[1]
      body.add quote do:
        when compiles(`a`.`k`):
          `a`.`k` = `v`
        elif compiles(`a`[`k`]):
          `a`[`k`] = `v`
        else:
          `a`[`kString`] = `v`

    else:
      error("Expression `" & $x.toStrLit & "` not allowed in `{}` macro")

  body.add quote do:
    return `a`

  result = quote do:
    proc inner(): `typ` {.gensym.} =
      `body`
    inner()

# Macro to build a lambda using JavaScript's `this`
# from a proc, `this` being the first argument.

macro bindMethod*(procedure: typed): auto =
  ## Takes the name of a procedure and wraps it into a lambda missing the first
  ## argument, which passes the JavaScript builtin ``this`` as the first
  ## argument to the procedure. Returns the resulting lambda.
  ##
  ## Example:
  ##
  ## We want to generate roughly this JavaScript:
  ##
  ## .. code-block:: js
  ##  var obj = {a: 10};
  ##  obj.someMethod = function() {
  ##    return this.a + 42;
  ##  };
  ##
  ## We can achieve this using the ``bindMethod`` macro:
  ##
  ## .. code-block:: nim
  ##  let obj = JsObject{ a: 10 }
  ##  proc someMethodImpl(that: JsObject): int =
  ##    that.a.to(int) + 42
  ##  obj.someMethod = bindMethod someMethodImpl
  ##
  ##  # Alternatively:
  ##  obj.someMethod = bindMethod
  ##    proc(that: JsObject): int = that.a.to(int) + 42
  if not (procedure.kind == nnkSym or procedure.kind == nnkLambda):
    error("Argument has to be a proc or a symbol corresponding to a proc.")
  var
    rawProc = if procedure.kind == nnkSym:
        getImpl(procedure.symbol)
      else:
        procedure
    args = rawProc[3]
    thisType = args[1][1]
    params = newNimNode(nnkFormalParams).add(args[0])
    body = newNimNode(nnkLambda)
    this = newIdentNode("this")
    # construct the `this` parameter:
    thisQuote = quote do:
      var `this` {. nodecl, importc .} : `thisType`
    call = newNimNode(nnkCall).add(rawProc[0], thisQuote[0][0][0])
  # construct the procedure call inside the method
  if args.len > 2:
    for idx in 2..args.len-1:
      params.add(args[idx])
      call.add(args[idx][0])
  body.add(newNimNode(nnkEmpty),
      rawProc[1],
      rawProc[2],
      params,
      rawProc[4],
      rawProc[5],
      newTree(nnkStmtList, thisQuote, call)
  )
  result = body
s="n">tyBool, tyEnum, tyChar, tyInt..tyUInt64} proc checkConvertible(c: PContext, castDest, src: PType): TConvStatus = result = convOK if sameType(castDest, src) and castDest.sym == src.sym: # don't annoy conversions that may be needed on another processor: if castDest.kind notin IntegralTypes+{tyRange}: result = convNotNeedeed return var d = skipTypes(castDest, abstractVar) var s = src if s.kind in tyUserTypeClasses and s.isResolvedUserTypeClass: s = s.lastSon s = skipTypes(s, abstractVar-{tyTypeDesc}) var pointers = 0 while (d != nil) and (d.kind in {tyPtr, tyRef}) and (d.kind == s.kind): d = d.lastSon s = s.lastSon inc pointers if d == nil: result = convNotLegal elif d.kind == tyObject and s.kind == tyObject: result = checkConversionBetweenObjects(d, s, pointers) elif (skipTypes(castDest, abstractVarRange).kind in IntegralTypes) and (skipTypes(src, abstractVarRange-{tyTypeDesc}).kind in IntegralTypes): # accept conversion between integral types discard else: # we use d, s here to speed up that operation a bit: case cmpTypes(c, d, s) of isNone, isGeneric: if not compareTypes(castDest, src, dcEqIgnoreDistinct): result = convNotLegal else: discard proc isCastable(conf: ConfigRef; dst, src: PType): bool = ## Checks whether the source type can be cast to the destination type. ## Casting is very unrestrictive; casts are allowed as long as ## castDest.size >= src.size, and typeAllowed(dst, skParam) #const # castableTypeKinds = {tyInt, tyPtr, tyRef, tyCstring, tyString, # tySequence, tyPointer, tyNil, tyOpenArray, # tyProc, tySet, tyEnum, tyBool, tyChar} let src = src.skipTypes(tyUserTypeClasses) if skipTypes(dst, abstractInst-{tyOpenArray}).kind == tyOpenArray: return false if skipTypes(src, abstractInst-{tyTypeDesc}).kind == tyTypeDesc: return false var dstSize, srcSize: BiggestInt dstSize = computeSize(conf, dst) srcSize = computeSize(conf, src) if dstSize < 0: result = false elif srcSize < 0: result = false elif typeAllowed(dst, skParam) != nil: result = false elif dst.kind == tyProc and dst.callConv == ccClosure: result = src.kind == tyProc and src.callConv == ccClosure else: result = (dstSize >= srcSize) or (skipTypes(dst, abstractInst).kind in IntegralTypes) or (skipTypes(src, abstractInst-{tyTypeDesc}).kind in IntegralTypes) if result and src.kind == tyNil: result = dst.size <= conf.target.ptrSize proc isSymChoice(n: PNode): bool {.inline.} = result = n.kind in nkSymChoices proc maybeLiftType(t: var PType, c: PContext, info: TLineInfo) = # XXX: liftParamType started to perform addDecl # we could do that instead in semTypeNode by snooping for added # gnrc. params, then it won't be necessary to open a new scope here openScope(c) var lifted = liftParamType(c, skType, newNodeI(nkArgList, info), t, ":anon", info) closeScope(c) if lifted != nil: t = lifted proc semConv(c: PContext, n: PNode): PNode = if sonsLen(n) != 2: localError(c.config, n.info, "a type conversion takes exactly one argument") return n result = newNodeI(nkConv, n.info) var targetType = semTypeNode(c, n.sons[0], nil) if targetType.kind == tyTypeDesc: internalAssert c.config, targetType.len > 0 if targetType.base.kind == tyNone: return semTypeOf(c, n[1]) else: targetType = targetType.base elif targetType.kind == tyStatic: var evaluated = semStaticExpr(c, n[1]) if evaluated.kind == nkType or evaluated.typ.kind == tyTypeDesc: result = n result.typ = c.makeTypeDesc semStaticType(c, evaluated, nil) return elif targetType.base.kind == tyNone: return evaluated else: targetType = targetType.base maybeLiftType(targetType, c, n[0].info) if targetType.kind in {tySink, tyLent}: let baseType = semTypeNode(c, n.sons[1], nil).skipTypes({tyTypeDesc}) let t = newTypeS(targetType.kind, c) t.rawAddSonNoPropagationOfTypeFlags baseType result = newNodeI(nkType, n.info) result.typ = makeTypeDesc(c, t) return result.addSon copyTree(n.sons[0]) # special case to make MyObject(x = 3) produce a nicer error message: if n[1].kind == nkExprEqExpr and targetType.skipTypes(abstractPtrs).kind == tyObject: localError(c.config, n.info, "object contruction uses ':', not '='") var op = semExprWithType(c, n.sons[1]) if targetType.isMetaType: let final = inferWithMetatype(c, targetType, op, true) result.addSon final result.typ = final.typ return result.typ = targetType # XXX op is overwritten later on, this is likely added too early # here or needs to be overwritten too then. addSon(result, op) if not isSymChoice(op): let status = checkConvertible(c, result.typ, op.typ) case status of convOK: # handle SomeProcType(SomeGenericProc) if op.kind == nkSym and op.sym.isGenericRoutine: result.sons[1] = fitNode(c, result.typ, result.sons[1], result.info) elif op.kind in {nkPar, nkTupleConstr} and targetType.kind == tyTuple: op = fitNode(c, targetType, op, result.info) of convNotNeedeed: message(c.config, n.info, hintConvFromXtoItselfNotNeeded, result.typ.typeToString) of convNotLegal: result = fitNode(c, result.typ, result.sons[1], result.info) if result == nil: localError(c.config, n.info, "illegal conversion from '$1' to '$2'" % [op.typ.typeToString, result.typ.typeToString]) else: for i in countup(0, sonsLen(op) - 1): let it = op.sons[i] let status = checkConvertible(c, result.typ, it.typ) if status in {convOK, convNotNeedeed}: markUsed(c.config, n.info, it.sym, c.graph.usageSym) styleCheckUse(n.info, it.sym) markIndirect(c, it.sym) return it errorUseQualifier(c, n.info, op.sons[0].sym) proc semCast(c: PContext, n: PNode): PNode = ## Semantically analyze a casting ("cast[type](param)") checkSonsLen(n, 2, c.config) let targetType = semTypeNode(c, n.sons[0], nil) let castedExpr = semExprWithType(c, n.sons[1]) if tfHasMeta in targetType.flags: localError(c.config, n.sons[0].info, "cannot cast to a non concrete type: '$1'" % $targetType) if not isCastable(c.config, targetType, castedExpr.typ): let tar = $targetType let alt = typeToString(targetType, preferDesc) let msg = if tar != alt: tar & "=" & alt else: tar localError(c.config, n.info, "expression cannot be cast to " & msg) result = newNodeI(nkCast, n.info) result.typ = targetType addSon(result, copyTree(n.sons[0])) addSon(result, castedExpr) proc semLowHigh(c: PContext, n: PNode, m: TMagic): PNode = const opToStr: array[mLow..mHigh, string] = ["low", "high"] if sonsLen(n) != 2: localError(c.config, n.info, errXExpectsTypeOrValue % opToStr[m]) else: n.sons[1] = semExprWithType(c, n.sons[1], {efDetermineType}) var typ = skipTypes(n.sons[1].typ, abstractVarRange + {tyTypeDesc, tyUserTypeClassInst}) case typ.kind of tySequence, tyString, tyCString, tyOpenArray, tyVarargs: n.typ = getSysType(c.graph, n.info, tyInt) of tyArray: n.typ = typ.sons[0] # indextype of tyInt..tyInt64, tyChar, tyBool, tyEnum, tyUInt8, tyUInt16, tyUInt32: # do not skip the range! n.typ = n.sons[1].typ.skipTypes(abstractVar) of tyGenericParam: # prepare this for resolving in semtypinst: # we must use copyTree here in order to avoid creating a cycle # that could easily turn into an infinite recursion in semtypinst n.typ = makeTypeFromExpr(c, n.copyTree) else: localError(c.config, n.info, "invalid argument for: " & opToStr[m]) result = n proc semSizeof(c: PContext, n: PNode): PNode = if sonsLen(n) != 2: localError(c.config, n.info, errXExpectsTypeOrValue % "sizeof") else: n.sons[1] = semExprWithType(c, n.sons[1], {efDetermineType}) #restoreOldStyleType(n.sons[1]) n.typ = getSysType(c.graph, n.info, tyInt) result = n proc fixupStaticType(c: PContext, n: PNode) = # This proc can be applied to evaluated expressions to assign # them a static type. # # XXX: with implicit static, this should not be necessary, # because the output type of operations such as `semConstExpr` # should be a static type (as well as the type of any other # expression that can be implicitly evaluated). For now, we # apply this measure only in code that is enlightened to work # with static types. if n.typ.kind != tyStatic: n.typ = newTypeWithSons(getCurrOwner(c), tyStatic, @[n.typ]) n.typ.n = n # XXX: cycles like the one here look dangerous. # Consider using `n.copyTree` proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): PNode = internalAssert c.config, n.sonsLen == 3 and n[1].typ != nil and n[2].kind in {nkStrLit..nkTripleStrLit, nkType} var res = false t1 = n[1].typ t2 = n[2].typ if t1.kind == tyTypeDesc and t2.kind != tyTypeDesc: t1 = t1.base if n[2].kind in {nkStrLit..nkTripleStrLit}: case n[2].strVal.normalize of "closure": let t = skipTypes(t1, abstractRange) res = t.kind == tyProc and t.callConv == ccClosure and tfIterator notin t.flags else: res = false else: maybeLiftType(t2, c, n.info) var m: TCandidate initCandidate(c, m, t2) if efExplain in flags: m.diagnostics = @[] m.diagnosticsEnabled = true res = typeRel(m, t2, t1) >= isSubtype # isNone result = newIntNode(nkIntLit, ord(res)) result.typ = n.typ proc semIs(c: PContext, n: PNode, flags: TExprFlags): PNode = if sonsLen(n) != 3: localError(c.config, n.info, "'is' operator takes 2 arguments") let boolType = getSysType(c.graph, n.info, tyBool) result = n n.typ = boolType var liftLhs = true n.sons[1] = semExprWithType(c, n[1], {efDetermineType, efWantIterator}) if n[2].kind notin {nkStrLit..nkTripleStrLit}: let t2 = semTypeNode(c, n[2], nil) n.sons[2] = newNodeIT(nkType, n[2].info, t2) if t2.kind == tyStatic: let evaluated = tryConstExpr(c, n[1]) if evaluated != nil: c.fixupStaticType(evaluated) n[1] = evaluated else: result = newIntNode(nkIntLit, 0) result.typ = boolType return elif t2.kind == tyTypeDesc and (t2.base.kind == tyNone or tfExplicit in t2.flags): # When the right-hand side is an explicit type, we must # not allow regular values to be matched against the type: liftLhs = false else: n.sons[2] = semExpr(c, n[2]) var lhsType = n[1].typ if lhsType.kind != tyTypeDesc: if liftLhs: n[1] = makeTypeSymNode(c, lhsType, n[1].info) lhsType = n[1].typ else: if lhsType.base.kind == tyNone: # this is a typedesc variable, leave for evals return if lhsType.base.containsGenericType: # BUGFIX: don't evaluate this too early: ``T is void`` return result = isOpImpl(c, n, flags) proc semOpAux(c: PContext, n: PNode) = const flags = {efDetermineType} for i in countup(1, n.sonsLen-1): var a = n.sons[i] if a.kind == nkExprEqExpr and sonsLen(a) == 2: let info = a.sons[0].info a.sons[0] = newIdentNode(considerQuotedIdent(c, a.sons[0], a), info) a.sons[1] = semExprWithType(c, a.sons[1], flags) a.typ = a.sons[1].typ else: n.sons[i] = semExprWithType(c, a, flags) proc overloadedCallOpr(c: PContext, n: PNode): PNode = # quick check if there is *any* () operator overloaded: var par = getIdent(c.cache, "()") if searchInScopes(c, par) == nil: result = nil else: result = newNodeI(nkCall, n.info) addSon(result, newIdentNode(par, n.info)) for i in countup(0, sonsLen(n) - 1): addSon(result, n.sons[i]) result = semExpr(c, result) proc changeType(c: PContext; n: PNode, newType: PType, check: bool) = case n.kind of nkCurly, nkBracket: for i in countup(0, sonsLen(n) - 1): changeType(c, n.sons[i], elemType(newType), check) of nkPar, nkTupleConstr: let tup = newType.skipTypes({tyGenericInst, tyAlias, tySink}) if tup.kind != tyTuple: if tup.kind == tyObject: return globalError(c.config, n.info, "no tuple type for constructor") elif sonsLen(n) > 0 and n.sons[0].kind == nkExprColonExpr: # named tuple? for i in countup(0, sonsLen(n) - 1): var m = n.sons[i].sons[0] if m.kind != nkSym: globalError(c.config, m.info, "invalid tuple constructor") return if tup.n != nil: var f = getSymFromList(tup.n, m.sym.name) if f == nil: globalError(c.config, m.info, "unknown identifier: " & m.sym.name.s) return changeType(c, n.sons[i].sons[1], f.typ, check) else: changeType(c, n.sons[i].sons[1], tup.sons[i], check) else: for i in countup(0, sonsLen(n) - 1): changeType(c, n.sons[i], tup.sons[i], check) when false: var m = n.sons[i] var a = newNodeIT(nkExprColonExpr, m.info, newType.sons[i]) addSon(a, newSymNode(newType.n.sons[i].sym)) addSon(a, m) changeType(m, tup.sons[i], check) of nkCharLit..nkUInt64Lit: if check and n.kind != nkUInt64Lit: let value = n.intVal if value < firstOrd(c.config, newType) or value > lastOrd(c.config, newType): localError(c.config, n.info, "cannot convert " & $value & " to " & typeToString(newType)) else: discard n.typ = newType proc arrayConstrType(c: PContext, n: PNode): PType = var typ = newTypeS(tyArray, c) rawAddSon(typ, nil) # index type if sonsLen(n) == 0: rawAddSon(typ, newTypeS(tyEmpty, c)) # needs an empty basetype! else: var t = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) addSonSkipIntLit(typ, t) typ.sons[0] = makeRangeType(c, 0, sonsLen(n) - 1, n.info) result = typ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = result = newNodeI(nkBracket, n.info) result.typ = newTypeS(tyArray, c) rawAddSon(result.typ, nil) # index type if sonsLen(n) == 0: rawAddSon(result.typ, newTypeS(tyEmpty, c)) # needs an empty basetype! else: var x = n.sons[0] var lastIndex: BiggestInt = 0 var indexType = getSysType(c.graph, n.info, tyInt) if x.kind == nkExprColonExpr and sonsLen(x) == 2: var idx = semConstExpr(c, x.sons[0]) lastIndex = getOrdValue(idx) indexType = idx.typ x = x.sons[1] let yy = semExprWithType(c, x) var typ = yy.typ addSon(result, yy) #var typ = skipTypes(result.sons[0].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal}) for i in countup(1, sonsLen(n) - 1): x = n.sons[i] if x.kind == nkExprColonExpr and sonsLen(x) == 2: var idx = semConstExpr(c, x.sons[0]) idx = fitNode(c, indexType, idx, x.info) if lastIndex+1 != getOrdValue(idx): localError(c.config, x.info, "invalid order in array constructor") x = x.sons[1] let xx = semExprWithType(c, x, flags*{efAllowDestructor}) result.add xx typ = commonType(typ, xx.typ) #n.sons[i] = semExprWithType(c, x, flags*{efAllowDestructor}) #addSon(result, fitNode(c, typ, n.sons[i])) inc(lastIndex) addSonSkipIntLit(result.typ, typ) for i in 0 ..< result.len: result.sons[i] = fitNode(c, typ, result.sons[i], result.sons[i].info) result.typ.sons[0] = makeRangeType(c, 0, sonsLen(result) - 1, n.info) proc fixAbstractType(c: PContext, n: PNode) = for i in 1 ..< n.len: let it = n.sons[i] # do not get rid of nkHiddenSubConv for OpenArrays, the codegen needs it: if it.kind == nkHiddenSubConv and skipTypes(it.typ, abstractVar).kind notin {tyOpenArray, tyVarargs}: if skipTypes(it.sons[1].typ, abstractVar).kind in {tyNil, tyTuple, tySet} or it[1].isArrayConstr: var s = skipTypes(it.typ, abstractVar) if s.kind != tyExpr: changeType(c, it.sons[1], s, check=true) n.sons[i] = it.sons[1] proc isAssignable(c: PContext, n: PNode; isUnsafeAddr=false): TAssignableResult = result = parampatterns.isAssignable(c.p.owner, n, isUnsafeAddr) proc isUnresolvedSym(s: PSym): bool = return s.kind == skGenericParam or tfInferrableStatic in s.typ.flags or (s.kind == skParam and s.typ.isMetaType) or (s.kind == skType and s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} != {}) proc hasUnresolvedArgs(c: PContext, n: PNode): bool = # Checks whether an expression depends on generic parameters that # don't have bound values yet. E.g. this could happen in situations # such as: # type Slot[T] = array[T.size, byte] # proc foo[T](x: default(T)) # # Both static parameter and type parameters can be unresolved. case n.kind of nkSym: return isUnresolvedSym(n.sym) of nkIdent, nkAccQuoted: let ident = considerQuotedIdent(c, n) let sym = searchInScopes(c, ident) if sym != nil: return isUnresolvedSym(sym) else: return false else: for i in 0..<n.safeLen: if hasUnresolvedArgs(c, n.sons[i]): return true return false proc newHiddenAddrTaken(c: PContext, n: PNode): PNode = if n.kind == nkHiddenDeref and not (c.config.cmd == cmdCompileToCpp or sfCompileToCpp in c.module.flags): checkSonsLen(n, 1, c.config) result = n.sons[0] else: result = newNodeIT(nkHiddenAddr, n.info, makeVarType(c, n.typ)) addSon(result, n) if isAssignable(c, n) notin {arLValue, arLocalLValue}: localError(c.config, n.info, errVarForOutParamNeededX % renderNotLValue(n)) proc analyseIfAddressTaken(c: PContext, n: PNode): PNode = result = n case n.kind of nkSym: # n.sym.typ can be nil in 'check' mode ... if n.sym.typ != nil and skipTypes(n.sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: incl(n.sym.flags, sfAddrTaken) result = newHiddenAddrTaken(c, n) of nkDotExpr: checkSonsLen(n, 2, c.config) if n.sons[1].kind != nkSym: internalError(c.config, n.info, "analyseIfAddressTaken") return if skipTypes(n.sons[1].sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: incl(n.sons[1].sym.flags, sfAddrTaken) result = newHiddenAddrTaken(c, n) of nkBracketExpr: checkMinSonsLen(n, 1, c.config) if skipTypes(n.sons[0].typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: if n.sons[0].kind == nkSym: incl(n.sons[0].sym.flags, sfAddrTaken) result = newHiddenAddrTaken(c, n) else: result = newHiddenAddrTaken(c, n) proc analyseIfAddressTakenInCall(c: PContext, n: PNode) = checkMinSonsLen(n, 1, c.config) const FakeVarParams = {mNew, mNewFinalize, mInc, ast.mDec, mIncl, mExcl, mSetLengthStr, mSetLengthSeq, mAppendStrCh, mAppendStrStr, mSwap, mAppendSeqElem, mNewSeq, mReset, mShallowCopy, mDeepCopy} # get the real type of the callee # it may be a proc var with a generic alias type, so we skip over them var t = n.sons[0].typ.skipTypes({tyGenericInst, tyAlias, tySink}) if n.sons[0].kind == nkSym and n.sons[0].sym.magic in FakeVarParams: # BUGFIX: check for L-Value still needs to be done for the arguments! # note sometimes this is eval'ed twice so we check for nkHiddenAddr here: for i in countup(1, sonsLen(n) - 1): if i < sonsLen(t) and t.sons[i] != nil and skipTypes(t.sons[i], abstractInst-{tyTypeDesc}).kind == tyVar: let it = n[i] if isAssignable(c, it) notin {arLValue, arLocalLValue}: if it.kind != nkHiddenAddr: localError(c.config, it.info, errVarForOutParamNeededX % $it) # bug #5113: disallow newSeq(result) where result is a 'var T': if n[0].sym.magic in {mNew, mNewFinalize, mNewSeq}: var arg = n[1] #.skipAddr if arg.kind == nkHiddenDeref: arg = arg[0] if arg.kind == nkSym and arg.sym.kind == skResult and arg.typ.skipTypes(abstractInst).kind in {tyVar, tyLent}: localError(c.config, n.info, errXStackEscape % renderTree(n[1], {renderNoComments})) return for i in countup(1, sonsLen(n) - 1): if n.sons[i].kind == nkHiddenCallConv: # we need to recurse explicitly here as converters can create nested # calls and then they wouldn't be analysed otherwise analyseIfAddressTakenInCall(c, n.sons[i]) if i < sonsLen(t) and skipTypes(t.sons[i], abstractInst-{tyTypeDesc}).kind == tyVar: if n.sons[i].kind != nkHiddenAddr: n.sons[i] = analyseIfAddressTaken(c, n.sons[i]) include semmagic proc evalAtCompileTime(c: PContext, n: PNode): PNode = result = n if n.kind notin nkCallKinds or n.sons[0].kind != nkSym: return var callee = n.sons[0].sym # workaround for bug #537 (overly aggressive inlining leading to # wrong NimNode semantics): if n.typ != nil and tfTriggersCompileTime in n.typ.flags: return # constant folding that is necessary for correctness of semantic pass: if callee.magic != mNone and callee.magic in ctfeWhitelist and n.typ != nil: var call = newNodeIT(nkCall, n.info, n.typ) call.add(n.sons[0]) var allConst = true for i in 1 ..< n.len: var a = getConstExpr(c.module, n.sons[i], c.graph) if a == nil: allConst = false a = n.sons[i] if a.kind == nkHiddenStdConv: a = a.sons[1] call.add(a) if allConst: result = semfold.getConstExpr(c.module, call, c.graph) if result.isNil: result = n else: return result block maybeLabelAsStatic: # XXX: temporary work-around needed for tlateboundstatic. # This is certainly not correct, but it will get the job # done until we have a more robust infrastructure for # implicit statics. if n.len > 1: for i in 1 ..< n.len: # see bug #2113, it's possible that n[i].typ for errornous code: if n[i].typ.isNil or n[i].typ.kind != tyStatic or tfUnresolved notin n[i].typ.flags: break maybeLabelAsStatic n.typ = newTypeWithSons(c, tyStatic, @[n.typ]) n.typ.flags.incl tfUnresolved # optimization pass: not necessary for correctness of the semantic pass if {sfNoSideEffect, sfCompileTime} * callee.flags != {} and {sfForward, sfImportc} * callee.flags == {} and n.typ != nil: if sfCompileTime notin callee.flags and optImplicitStatic notin c.config.options: return if callee.magic notin ctfeWhitelist: return if callee.kind notin {skProc, skFunc, skConverter} or callee.isGenericRoutine: return if n.typ != nil and typeAllowed(n.typ, skConst) != nil: return var call = newNodeIT(nkCall, n.info, n.typ) call.add(n.sons[0]) for i in 1 ..< n.len: let a = getConstExpr(c.module, n.sons[i], c.graph) if a == nil: return n call.add(a) #echo "NOW evaluating at compile time: ", call.renderTree if sfCompileTime in callee.flags: result = evalStaticExpr(c.module, c.graph, call, c.p.owner) if result.isNil: localError(c.config, n.info, errCannotInterpretNodeX % renderTree(call)) else: result = fixupTypeAfterEval(c, result, n) else: result = evalConstExpr(c.module, c.graph, call) if result.isNil: result = n else: result = fixupTypeAfterEval(c, result, n) #if result != n: # echo "SUCCESS evaluated at compile time: ", call.renderTree proc semStaticExpr(c: PContext, n: PNode): PNode = let a = semExpr(c, n) if a.findUnresolvedStatic != nil: return a result = evalStaticExpr(c.module, c.graph, a, c.p.owner) if result.isNil: localError(c.config, n.info, errCannotInterpretNodeX % renderTree(n)) result = c.graph.emptyNode else: result = fixupTypeAfterEval(c, result, a) proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode, flags: TExprFlags): PNode = if flags*{efInTypeof, efWantIterator} != {}: # consider: 'for x in pReturningArray()' --> we don't want the restriction # to 'skIterator' anymore; skIterator is preferred in sigmatch already # for typeof support. # for ``type(countup(1,3))``, see ``tests/ttoseq``. result = semOverloadedCall(c, n, nOrig, {skProc, skFunc, skMethod, skConverter, skMacro, skTemplate, skIterator}, flags) else: result = semOverloadedCall(c, n, nOrig, {skProc, skFunc, skMethod, skConverter, skMacro, skTemplate}, flags) if result != nil: if result.sons[0].kind != nkSym: internalError(c.config, "semOverloadedCallAnalyseEffects") return let callee = result.sons[0].sym case callee.kind of skMacro, skTemplate: discard else: if callee.kind == skIterator and callee.id == c.p.owner.id: localError(c.config, n.info, errRecursiveDependencyX % callee.name.s) # error correction, prevents endless for loop elimination in transf. # See bug #2051: result.sons[0] = newSymNode(errorSym(c, n)) proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode proc resolveIndirectCall(c: PContext; n, nOrig: PNode; t: PType): TCandidate = initCandidate(c, result, t) matches(c, n, nOrig, result) if result.state != csMatch: # try to deref the first argument: if implicitDeref in c.features and canDeref(n): n.sons[1] = n.sons[1].tryDeref initCandidate(c, result, t) matches(c, n, nOrig, result) proc bracketedMacro(n: PNode): PSym = if n.len >= 1 and n[0].kind == nkSym: result = n[0].sym if result.kind notin {skMacro, skTemplate}: result = nil proc setGenericParams(c: PContext, n: PNode) = for i in 1 ..< n.len: n[i].typ = semTypeNode(c, n[i], nil) proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags): PNode = result = n let callee = result.sons[0].sym case callee.kind of skMacro: result = semMacroExpr(c, result, orig, callee, flags) of skTemplate: result = semTemplateExpr(c, result, callee, flags) else: semFinishOperands(c, result) activate(c, result) fixAbstractType(c, result) analyseIfAddressTakenInCall(c, result) if callee.magic != mNone: result = magicsAfterOverloadResolution(c, result, flags) if result.typ != nil: liftTypeBoundOps(c, result.typ, n.info) #result = patchResolvedTypeBoundOp(c, result) if c.matchedConcept == nil: result = evalAtCompileTime(c, result) proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = result = nil checkMinSonsLen(n, 1, c.config) var prc = n.sons[0] if n.sons[0].kind == nkDotExpr: checkSonsLen(n.sons[0], 2, c.config) let n0 = semFieldAccess(c, n.sons[0]) if n0.kind == nkDotCall: # it is a static call! result = n0 result.kind = nkCall result.flags.incl nfExplicitCall for i in countup(1, sonsLen(n) - 1): addSon(result, n.sons[i]) return semExpr(c, result, flags) else: n.sons[0] = n0 else: n.sons[0] = semExpr(c, n.sons[0], {efInCall}) let t = n.sons[0].typ if t != nil and t.kind in {tyVar, tyLent}: n.sons[0] = newDeref(n.sons[0]) elif n.sons[0].kind == nkBracketExpr: let s = bracketedMacro(n.sons[0]) if s != nil: setGenericParams(c, n[0]) return semDirectOp(c, n, flags) let nOrig = n.copyTree semOpAux(c, n) var t: PType = nil if n.sons[0].typ != nil: t = skipTypes(n.sons[0].typ, abstractInst-{tyTypeDesc}) if t != nil and t.kind == tyProc: # This is a proc variable, apply normal overload resolution let m = resolveIndirectCall(c, n, nOrig, t) if m.state != csMatch: if c.config.m.errorOutputs == {}: # speed up error generation: globalError(c.config, n.info, "type mismatch") return c.graph.emptyNode else: var hasErrorType = false var msg = "type mismatch: got <" for i in countup(1, sonsLen(n) - 1): if i > 1: add(msg, ", ") let nt = n.sons[i].typ add(msg, typeToString(nt)) if nt.kind == tyError: hasErrorType = true break if not hasErrorType: add(msg, ">\nbut expected one of: \n" & typeToString(n.sons[0].typ)) localError(c.config, n.info, msg) return errorNode(c, n) result = nil else: result = m.call instGenericConvertersSons(c, result, m) elif t != nil and t.kind == tyTypeDesc: if n.len == 1: return semObjConstr(c, n, flags) return semConv(c, n) else: result = overloadedCallOpr(c, n) # Now that nkSym does not imply an iteration over the proc/iterator space, # the old ``prc`` (which is likely an nkIdent) has to be restored: if result == nil: # XXX: hmm, what kind of symbols will end up here? # do we really need to try the overload resolution? n.sons[0] = prc nOrig.sons[0] = prc n.flags.incl nfExprCall result = semOverloadedCallAnalyseEffects(c, n, nOrig, flags) if result == nil: return errorNode(c, n) elif result.kind notin nkCallKinds: # the semExpr() in overloadedCallOpr can even break this condition! # See bug #904 of how to trigger it: return result #result = afterCallActions(c, result, nOrig, flags) if result.sons[0].kind == nkSym: result = afterCallActions(c, result, nOrig, flags) else: fixAbstractType(c, result) analyseIfAddressTakenInCall(c, result) proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = # this seems to be a hotspot in the compiler! let nOrig = n.copyTree #semLazyOpAux(c, n) result = semOverloadedCallAnalyseEffects(c, n, nOrig, flags) if result != nil: result = afterCallActions(c, result, nOrig, flags) else: result = errorNode(c, n) proc buildEchoStmt(c: PContext, n: PNode): PNode = # we MUST not check 'n' for semantics again here! But for now we give up: result = newNodeI(nkCall, n.info) var e = strTableGet(c.graph.systemModule.tab, getIdent(c.cache, "echo")) if e != nil: add(result, newSymNode(e)) else: localError(c.config, n.info, "system needs: echo") add(result, errorNode(c, n)) add(result, n) result = semExpr(c, result) proc semExprNoType(c: PContext, n: PNode): PNode = result = semExpr(c, n, {efWantStmt}) discardCheck(c, result) proc isTypeExpr(n: PNode): bool = case n.kind of nkType, nkTypeOfExpr: result = true of nkSym: result = n.sym.kind == skType else: result = false proc createSetType(c: PContext; baseType: PType): PType = assert baseType != nil result = newTypeS(tySet, c) rawAddSon(result, baseType) proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent, check: var PNode): PSym = # transform in a node that contains the runtime check for the # field, if it is in a case-part... result = nil case r.kind of nkRecList: for i in countup(0, sonsLen(r) - 1): result = lookupInRecordAndBuildCheck(c, n, r.sons[i], field, check) if result != nil: return of nkRecCase: checkMinSonsLen(r, 2, c.config) if (r.sons[0].kind != nkSym): illFormedAst(r, c.config) result = lookupInRecordAndBuildCheck(c, n, r.sons[0], field, check) if result != nil: return let setType = createSetType(c, r.sons[0].typ) var s = newNodeIT(nkCurly, r.info, setType) for i in countup(1, sonsLen(r) - 1): var it = r.sons[i] case it.kind of nkOfBranch: result = lookupInRecordAndBuildCheck(c, n, lastSon(it), field, check) if result == nil: for j in 0..sonsLen(it)-2: addSon(s, copyTree(it.sons[j])) else: if check == nil: check = newNodeI(nkCheckedFieldExpr, n.info) addSon(check, c.graph.emptyNode) # make space for access node s = newNodeIT(nkCurly, n.info, setType) for j in countup(0, sonsLen(it) - 2): addSon(s, copyTree(it.sons[j])) var inExpr = newNodeIT(nkCall, n.info, getSysType(c.graph, n.info, tyBool)) addSon(inExpr, newSymNode(c.graph.opContains, n.info)) addSon(inExpr, s) addSon(inExpr, copyTree(r.sons[0])) addSon(check, inExpr) #addSon(check, semExpr(c, inExpr)) return of nkElse: result = lookupInRecordAndBuildCheck(c, n, lastSon(it), field, check) if result != nil: if check == nil: check = newNodeI(nkCheckedFieldExpr, n.info) addSon(check, c.graph.emptyNode) # make space for access node var inExpr = newNodeIT(nkCall, n.info, getSysType(c.graph, n.info, tyBool)) addSon(inExpr, newSymNode(c.graph.opContains, n.info)) addSon(inExpr, s) addSon(inExpr, copyTree(r.sons[0])) var notExpr = newNodeIT(nkCall, n.info, getSysType(c.graph, n.info, tyBool)) addSon(notExpr, newSymNode(c.graph.opNot, n.info)) addSon(notExpr, inExpr) addSon(check, notExpr) return else: illFormedAst(it, c.config) of nkSym: if r.sym.name.id == field.id: result = r.sym else: illFormedAst(n, c.config) const tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass} tyDotOpTransparent = {tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink} proc readTypeParameter(c: PContext, typ: PType, paramName: PIdent, info: TLineInfo): PNode = # Note: This function will return emptyNode when attempting to read # a static type parameter that is not yet resolved (e.g. this may # happen in proc signatures such as `proc(x: T): array[T.sizeParam, U]` if typ.kind in {tyUserTypeClass, tyUserTypeClassInst}: for statement in typ.n: case statement.kind of nkTypeSection: for def in statement: if def[0].sym.name.id == paramName.id: # XXX: Instead of lifting the section type to a typedesc # here, we could try doing it earlier in semTypeSection. # This seems semantically correct and then we'll be able # to return the section symbol directly here let foundType = makeTypeDesc(c, def[2].typ) return newSymNode(copySym(def[0].sym).linkTo(foundType), info) of nkConstSection: for def in statement: if def[0].sym.name.id == paramName.id: return def[2] else: discard if typ.kind != tyUserTypeClass: let ty = if typ.kind == tyCompositeTypeClass: typ.sons[1].skipGenericAlias else: typ.skipGenericAlias let tbody = ty.sons[0] for s in countup(0, tbody.len-2): let tParam = tbody.sons[s] if tParam.sym.name.id == paramName.id: let rawTyp = ty.sons[s + 1] if rawTyp.kind == tyStatic: if rawTyp.n != nil: return rawTyp.n else: return c.graph.emptyNode else: let foundTyp = makeTypeDesc(c, rawTyp) return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info) return nil proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = let s = getGenSym(c, sym) case s.kind of skConst: markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) case skipTypes(s.typ, abstractInst-{tyTypeDesc}).kind of tyNil, tyChar, tyInt..tyInt64, tyFloat..tyFloat128, tyTuple, tySet, tyUInt..tyUInt64: if s.magic == mNone: result = inlineConst(c, n, s) else: result = newSymNode(s, n.info) of tyArray, tySequence: # Consider:: # const x = [] # proc p(a: openarray[int]) # proc q(a: openarray[char]) # p(x) # q(x) # # It is clear that ``[]`` means two totally different things. Thus, we # copy `x`'s AST into each context, so that the type fixup phase can # deal with two different ``[]``. if s.ast.len == 0: result = inlineConst(c, n, s) else: result = newSymNode(s, n.info) else: result = newSymNode(s, n.info) of skMacro: if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or (n.kind notin nkCallKinds and s.requiredParams > 0): markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = symChoice(c, n, s, scClosed) else: result = semMacroExpr(c, n, n, s, flags) of skTemplate: if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or (n.kind notin nkCallKinds and s.requiredParams > 0) or sfCustomPragma in sym.flags: markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = symChoice(c, n, s, scClosed) else: result = semTemplateExpr(c, n, s, flags) of skParam: markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) if s.typ != nil and s.typ.kind == tyStatic and s.typ.n != nil: # XXX see the hack in sigmatch.nim ... return s.typ.n elif sfGenSym in s.flags: if c.p.wasForwarded: # gensym'ed parameters that nevertheless have been forward declared # need a special fixup: let realParam = c.p.owner.typ.n[s.position+1] internalAssert c.config, realParam.kind == nkSym and realParam.sym.kind == skParam return newSymNode(c.p.owner.typ.n[s.position+1].sym, n.info) elif c.p.owner.kind == skMacro: # gensym'ed macro parameters need a similar hack (see bug #1944): var u = searchInScopes(c, s.name) internalAssert c.config, u != nil and u.kind == skParam and u.owner == s.owner return newSymNode(u, n.info) result = newSymNode(s, n.info) of skVar, skLet, skResult, skForVar: if s.magic == mNimvm: localError(c.config, n.info, "illegal context for 'nimvm' magic") markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) # We cannot check for access to outer vars for example because it's still # not sure the symbol really ends up being used: # var len = 0 # but won't be called # genericThatUsesLen(x) # marked as taking a closure? of skGenericParam: styleCheckUse(n.info, s) if s.typ.kind == tyStatic: result = newSymNode(s, n.info) result.typ = s.typ elif s.ast != nil: result = semExpr(c, s.ast) else: n.typ = s.typ return n of skType: markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) if s.typ.kind == tyStatic and s.typ.base.kind != tyNone and s.typ.n != nil: return s.typ.n result = newSymNode(s, n.info) result.typ = makeTypeDesc(c, s.typ) of skField: var p = c.p while p != nil and p.selfSym == nil: p = p.next if p != nil and p.selfSym != nil: var ty = skipTypes(p.selfSym.typ, {tyGenericInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink}) while tfBorrowDot in ty.flags: ty = ty.skipTypes({tyDistinct}) var check: PNode = nil if ty.kind == tyObject: while true: check = nil let f = lookupInRecordAndBuildCheck(c, n, ty.n, s.name, check) if f != nil and fieldVisible(c, f): # is the access to a public field or in the same module or in a friend? doAssert f == s markUsed(c.config, n.info, f, c.graph.usageSym) styleCheckUse(n.info, f) result = newNodeIT(nkDotExpr, n.info, f.typ) result.add makeDeref(newSymNode(p.selfSym)) result.add newSymNode(f) # we now have the correct field if check != nil: check.sons[0] = result check.typ = result.typ result = check return result if ty.sons[0] == nil: break ty = skipTypes(ty.sons[0], skipPtrs) # old code, not sure if it's live code: markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) else: markUsed(c.config, n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = ## returns nil if it's not a built-in field access checkSonsLen(n, 2, c.config) # tests/bind/tbindoverload.nim wants an early exit here, but seems to # work without now. template/tsymchoicefield doesn't like an early exit # here at all! #if isSymChoice(n.sons[1]): return when defined(nimsuggest): if c.config.cmd == cmdIdeTools: suggestExpr(c, n) if exactEquals(c.config.m.trackPos, n[1].info): suggestExprNoCheck(c, n) var s = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared, checkModule}) if s != nil: if s.kind in OverloadableSyms: result = symChoice(c, n, s, scClosed) if result.kind == nkSym: result = semSym(c, n, s, flags) else: markUsed(c.config, n.sons[1].info, s, c.graph.usageSym) result = semSym(c, n, s, flags) styleCheckUse(n.sons[1].info, s) return n.sons[0] = semExprWithType(c, n.sons[0], flags+{efDetermineType}) #restoreOldStyleType(n.sons[0]) var i = considerQuotedIdent(c, n.sons[1], n) var ty = n.sons[0].typ var f: PSym = nil result = nil template tryReadingGenericParam(t: PType) = case t.kind of tyTypeParamsHolders: result = readTypeParameter(c, t, i, n.info) if result == c.graph.emptyNode: result = n n.typ = makeTypeFromExpr(c, n.copyTree) return of tyUserTypeClasses: if t.isResolvedUserTypeClass: return readTypeParameter(c, t, i, n.info) else: n.typ = makeTypeFromExpr(c, copyTree(n)) return n of tyGenericParam, tyAnything: n.typ = makeTypeFromExpr(c, copyTree(n)) return n else: discard var argIsType = false if ty.kind == tyTypeDesc: if ty.base.kind == tyNone: # This is a still unresolved typedesc parameter. # If this is a regular proc, then all bets are off and we must return # tyFromExpr, but when this happen in a macro this is not a built-in # field access and we leave the compiler to compile a normal call: if getCurrOwner(c).kind != skMacro: n.typ = makeTypeFromExpr(c, n.copyTree) return n else: return nil else: ty = ty.base argIsType = true else: argIsType = isTypeExpr(n.sons[0]) if argIsType: ty = ty.skipTypes(tyDotOpTransparent) case ty.kind of tyEnum: # look up if the identifier belongs to the enum: while ty != nil: f = getSymFromList(ty.n, i) if f != nil: break ty = ty.sons[0] # enum inheritance if f != nil: result = newSymNode(f) result.info = n.info result.typ = ty markUsed(c.config, n.info, f, c.graph.usageSym) styleCheckUse(n.info, f) return of tyObject, tyTuple: if ty.n != nil and ty.n.kind == nkRecList: let field = lookupInRecord(ty.n, i) if field != nil: n.typ = makeTypeDesc(c, field.typ) return n else: tryReadingGenericParam(ty) return # XXX: This is probably not relevant any more # reset to prevent 'nil' bug: see "tests/reject/tenumitems.nim": ty = n.sons[0].typ return nil if ty.kind in tyUserTypeClasses and ty.isResolvedUserTypeClass: ty = ty.lastSon ty = skipTypes(ty, {tyGenericInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink}) while tfBorrowDot in ty.flags: ty = ty.skipTypes({tyDistinct}) var check: PNode = nil if ty.kind == tyObject: while true: check = nil f = lookupInRecordAndBuildCheck(c, n, ty.n, i, check) if f != nil: break if ty.sons[0] == nil: break ty = skipTypes(ty.sons[0], skipPtrs) if f != nil: if fieldVisible(c, f): # is the access to a public field or in the same module or in a friend? markUsed(c.config, n.sons[1].info, f, c.graph.usageSym) styleCheckUse(n.sons[1].info, f) n.sons[0] = makeDeref(n.sons[0]) n.sons[1] = newSymNode(f) # we now have the correct field n.typ = f.typ if check == nil: result = n else: check.sons[0] = n check.typ = n.typ result = check elif ty.kind == tyTuple and ty.n != nil: f = getSymFromList(ty.n, i) if f != nil: markUsed(c.config, n.sons[1].info, f, c.graph.usageSym) styleCheckUse(n.sons[1].info, f) n.sons[0] = makeDeref(n.sons[0]) n.sons[1] = newSymNode(f) n.typ = f.typ result = n # we didn't find any field, let's look for a generic param if result == nil: let t = n.sons[0].typ.skipTypes(tyDotOpTransparent) tryReadingGenericParam(t) proc dotTransformation(c: PContext, n: PNode): PNode = if isSymChoice(n.sons[1]): result = newNodeI(nkDotCall, n.info) addSon(result, n.sons[1]) addSon(result, copyTree(n[0])) else: var i = considerQuotedIdent(c, n.sons[1], n) result = newNodeI(nkDotCall, n.info) result.flags.incl nfDotField addSon(result, newIdentNode(i, n[1].info)) addSon(result, copyTree(n[0])) proc semFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = # this is difficult, because the '.' is used in many different contexts # in Nim. We first allow types in the semantic checking. result = builtinFieldAccess(c, n, flags) if result == nil: result = dotTransformation(c, n) proc buildOverloadedSubscripts(n: PNode, ident: PIdent): PNode = result = newNodeI(nkCall, n.info) result.add(newIdentNode(ident, n.info)) for i in 0 .. n.len-1: result.add(n[i]) proc semDeref(c: PContext, n: PNode): PNode = checkSonsLen(n, 1, c.config) n.sons[0] = semExprWithType(c, n.sons[0]) result = n var t = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyLent, tyAlias, tySink}) case t.kind of tyRef, tyPtr: n.typ = t.lastSon else: result = nil #GlobalError(n.sons[0].info, errCircumNeedsPointer) proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = ## returns nil if not a built-in subscript operator; also called for the ## checking of assignments if sonsLen(n) == 1: let x = semDeref(c, n) if x == nil: return nil result = newNodeIT(nkDerefExpr, x.info, x.typ) result.add(x[0]) return checkMinSonsLen(n, 2, c.config) # make sure we don't evaluate generic macros/templates n.sons[0] = semExprWithType(c, n.sons[0], {efNoEvaluateGeneric}) var arr = skipTypes(n.sons[0].typ, {tyGenericInst, tyUserTypeClassInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink}) if arr.kind == tyStatic: if arr.base.kind == tyNone: result = n result.typ = semStaticType(c, n[1], nil) return elif arr.n != nil: return semSubscript(c, arr.n, flags) else: arr = arr.base case arr.kind of tyArray, tyOpenArray, tyVarargs, tySequence, tyString, tyCString: if n.len != 2: return nil n.sons[0] = makeDeref(n.sons[0]) for i in countup(1, sonsLen(n) - 1): n.sons[i] = semExprWithType(c, n.sons[i], flags*{efInTypeof, efDetermineType}) var indexType = if arr.kind == tyArray: arr.sons[0] else: getSysType(c.graph, n.info, tyInt) var arg = indexTypesMatch(c, indexType, n.sons[1].typ, n.sons[1]) if arg != nil: n.sons[1] = arg result = n result.typ = elemType(arr) #GlobalError(n.info, errIndexTypesDoNotMatch) of tyTypeDesc: # The result so far is a tyTypeDesc bound # a tyGenericBody. The line below will substitute # it with the instantiated type. result = n result.typ = makeTypeDesc(c, semTypeNode(c, n, nil)) #result = symNodeFromType(c, semTypeNode(c, n, nil), n.info) of tyTuple: if n.len != 2: return nil n.sons[0] = makeDeref(n.sons[0]) # [] operator for tuples requires constant expression: n.sons[1] = semConstExpr(c, n.sons[1]) if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal, tyAlias, tySink}).kind in {tyInt..tyInt64}: var idx = getOrdValue(n.sons[1]) if idx >= 0 and idx < sonsLen(arr): n.typ = arr.sons[int(idx)] else: localError(c.config, n.info, "invalid index value for tuple subscript") result = n else: result = nil else: let s = if n.sons[0].kind == nkSym: n.sons[0].sym elif n[0].kind in nkSymChoices: n.sons[0][0].sym else: nil if s != nil: case s.kind of skProc, skFunc, skMethod, skConverter, skIterator: # type parameters: partial generic specialization n.sons[0] = semSymGenericInstantiation(c, n.sons[0], s) result = explicitGenericInstantiation(c, n, s) of skMacro, skTemplate: if efInCall in flags: # We are processing macroOrTmpl[] in macroOrTmpl[](...) call. # Return as is, so it can be transformed into complete macro or # template call in semIndirectOp caller. result = n else: # We are processing macroOrTmpl[] not in call. Transform it to the # macro or template call with generic arguments here. n.kind = nkCall case s.kind of skMacro: result = semMacroExpr(c, n, n, s, flags) of skTemplate: result = semTemplateExpr(c, n, s, flags) else: discard of skType: result = symNodeFromType(c, semTypeNode(c, n, nil), n.info) else: discard proc semArrayAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = result = semSubscript(c, n, flags) if result == nil: # overloaded [] operator: result = semExpr(c, buildOverloadedSubscripts(n, getIdent(c.cache, "[]"))) proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode = var id = considerQuotedIdent(c, a[1], a) var setterId = newIdentNode(getIdent(c.cache, id.s & '='), n.info) # a[0] is already checked for semantics, that does ``builtinFieldAccess`` # this is ugly. XXX Semantic checking should use the ``nfSem`` flag for # nodes? let aOrig = nOrig[0] result = newNode(nkCall, n.info, sons = @[setterId, a[0], semExprWithType(c, n[1])]) result.flags.incl nfDotSetter let orig = newNode(nkCall, n.info, sons = @[setterId, aOrig[0], nOrig[1]]) result = semOverloadedCallAnalyseEffects(c, result, orig, {}) if result != nil: result = afterCallActions(c, result, nOrig, {}) #fixAbstractType(c, result) #analyseIfAddressTakenInCall(c, result) proc takeImplicitAddr(c: PContext, n: PNode; isLent: bool): PNode = # See RFC #7373, calls returning 'var T' are assumed to # return a view into the first argument (if there is one): let root = exprRoot(n) if root != nil and root.owner == c.p.owner: if root.kind in {skLet, skVar, skTemp} and sfGlobal notin root.flags: localError(c.config, n.info, "'$1' escapes its stack frame; context: '$2'; see $3/var_t_return.html" % [ root.name.s, renderTree(n, {renderNoComments}), explanationsBaseUrl]) elif root.kind == skParam and root.position != 0: localError(c.config, n.info, "'$1' is not the first parameter; context: '$2'; see $3/var_t_return.html" % [ root.name.s, renderTree(n, {renderNoComments}), explanationsBaseUrl]) case n.kind of nkHiddenAddr, nkAddr: return n of nkHiddenDeref, nkDerefExpr: return n.sons[0] of nkBracketExpr: if len(n) == 1: return n.sons[0] else: discard let valid = isAssignable(c, n) if valid != arLValue: if valid == arLocalLValue: localError(c.config, n.info, errXStackEscape % renderTree(n, {renderNoComments})) elif not isLent: localError(c.config, n.info, errExprHasNoAddress) result = newNodeIT(nkHiddenAddr, n.info, makePtrType(c, n.typ)) result.add(n) proc asgnToResultVar(c: PContext, n, le, ri: PNode) {.inline.} = if le.kind == nkHiddenDeref: var x = le.sons[0] if x.typ.kind in {tyVar, tyLent} and x.kind == nkSym and x.sym.kind == skResult: n.sons[0] = x # 'result[]' --> 'result' n.sons[1] = takeImplicitAddr(c, ri, x.typ.kind == tyLent) x.typ.flags.incl tfVarIsPtr #echo x.info, " setting it for this type ", typeToString(x.typ), " ", n.info template resultTypeIsInferrable(typ: PType): untyped = typ.isMetaType and typ.kind != tyTypeDesc proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = checkSonsLen(n, 2, c.config) var a = n.sons[0] case a.kind of nkDotExpr: # r.f = x # --> `f=` (r, x) let nOrig = n.copyTree a = builtinFieldAccess(c, a, {efLValue}) if a == nil: a = propertyWriteAccess(c, n, nOrig, n[0]) if a != nil: return a # we try without the '='; proc that return 'var' or macros are still # possible: a = dotTransformation(c, n[0]) if a.kind == nkDotCall: a.kind = nkCall a = semExprWithType(c, a, {efLValue}) of nkBracketExpr: # a[i] = x # --> `[]=`(a, i, x) a = semSubscript(c, a, {efLValue}) if a == nil: result = buildOverloadedSubscripts(n.sons[0], getIdent(c.cache, "[]=")) add(result, n[1]) if mode == noOverloadedSubscript: bracketNotFoundError(c, result) return n else: result = semExprNoType(c, result) return result of nkCurlyExpr: # a{i} = x --> `{}=`(a, i, x) result = buildOverloadedSubscripts(n.sons[0], getIdent(c.cache, "{}=")) add(result, n[1]) return semExprNoType(c, result) of nkPar, nkTupleConstr: if a.len >= 2: # unfortunately we need to rewrite ``(x, y) = foo()`` already here so # that overloading of the assignment operator still works. Usually we # prefer to do these rewritings in transf.nim: return semStmt(c, lowerTupleUnpackingForAsgn(c.graph, n, c.p.owner)) else: a = semExprWithType(c, a, {efLValue}) else: a = semExprWithType(c, a, {efLValue}) n.sons[0] = a # a = b # both are vars, means: a[] = b[] # a = b # b no 'var T' means: a = addr(b) var le = a.typ if (skipTypes(le, {tyGenericInst, tyAlias, tySink}).kind != tyVar and isAssignable(c, a) == arNone) or skipTypes(le, abstractVar).kind in {tyOpenArray, tyVarargs}: # Direct assignment to a discriminant is allowed! localError(c.config, a.info, errXCannotBeAssignedTo % renderTree(a, {renderNoComments})) else: let lhs = n.sons[0] lhsIsResult = lhs.kind == nkSym and lhs.sym.kind == skResult var rhs = semExprWithType(c, n.sons[1], if lhsIsResult: {efAllowDestructor} else: {}) if lhsIsResult: n.typ = c.enforceVoidContext if c.p.owner.kind != skMacro and resultTypeIsInferrable(lhs.sym.typ): var rhsTyp = rhs.typ if rhsTyp.kind in tyUserTypeClasses and rhsTyp.isResolvedUserTypeClass: rhsTyp = rhsTyp.lastSon if cmpTypes(c, lhs.typ, rhsTyp) in {isGeneric, isEqual}: internalAssert c.config, c.p.resultSym != nil lhs.typ = rhsTyp c.p.resultSym.typ = rhsTyp c.p.owner.typ.sons[0] = rhsTyp else: typeMismatch(c.config, n.info, lhs.typ, rhsTyp) n.sons[1] = fitNode(c, le, rhs, n.info) liftTypeBoundOps(c, lhs.typ, lhs.info) fixAbstractType(c, n) asgnToResultVar(c, n, n.sons[0], n.sons[1]) result = n proc semReturn(c: PContext, n: PNode): PNode = result = n checkSonsLen(n, 1, c.config) if c.p.owner.kind in {skConverter, skMethod, skProc, skFunc, skMacro} or ( c.p.owner.kind == skIterator and c.p.owner.typ.callConv == ccClosure): if n.sons[0].kind != nkEmpty: # transform ``return expr`` to ``result = expr; return`` if c.p.resultSym != nil: var a = newNodeI(nkAsgn, n.sons[0].info) addSon(a, newSymNode(c.p.resultSym)) addSon(a, n.sons[0]) n.sons[0] = semAsgn(c, a) # optimize away ``result = result``: if n[0][1].kind == nkSym and n[0][1].sym == c.p.resultSym: n.sons[0] = c.graph.emptyNode else: localError(c.config, n.info, errNoReturnTypeDeclared) else: localError(c.config, n.info, "'return' not allowed here") proc semProcBody(c: PContext, n: PNode): PNode = openScope(c) result = semExpr(c, n) if c.p.resultSym != nil and not isEmptyType(result.typ): if result.kind == nkNilLit: # or ImplicitlyDiscardable(result): # new semantic: 'result = x' triggers the void context result.typ = nil elif result.kind == nkStmtListExpr and result.typ.kind == tyNil: # to keep backwards compatibility bodies like: # nil # # comment # are not expressions: fixNilType(c, result) else: var a = newNodeI(nkAsgn, n.info, 2) a.sons[0] = newSymNode(c.p.resultSym) a.sons[1] = result result = semAsgn(c, a) else: discardCheck(c, result) if c.p.owner.kind notin {skMacro, skTemplate} and c.p.resultSym != nil and c.p.resultSym.typ.isMetaType: if isEmptyType(result.typ): # we inferred a 'void' return type: c.p.resultSym.typ = errorType(c) c.p.owner.typ.sons[0] = nil else: localError(c.config, c.p.resultSym.info, errCannotInferReturnType) closeScope(c) proc semYieldVarResult(c: PContext, n: PNode, restype: PType) = var t = skipTypes(restype, {tyGenericInst, tyAlias, tySink}) case t.kind of tyVar, tyLent: if t.kind == tyVar: t.flags.incl tfVarIsPtr # bugfix for #4048, #4910, #6892 if n.sons[0].kind in {nkHiddenStdConv, nkHiddenSubConv}: n.sons[0] = n.sons[0].sons[1] n.sons[0] = takeImplicitAddr(c, n.sons[0], t.kind == tyLent) of tyTuple: for i in 0..<t.sonsLen: var e = skipTypes(t.sons[i], {tyGenericInst, tyAlias, tySink}) if e.kind in {tyVar, tyLent}: if e.kind == tyVar: e.flags.incl tfVarIsPtr # bugfix for #4048, #4910, #6892 if n.sons[0].kind in {nkPar, nkTupleConstr}: n.sons[0].sons[i] = takeImplicitAddr(c, n.sons[0].sons[i], e.kind == tyLent) elif n.sons[0].kind in {nkHiddenStdConv, nkHiddenSubConv} and n.sons[0].sons[1].kind in {nkPar, nkTupleConstr}: var a = n.sons[0].sons[1] a.sons[i] = takeImplicitAddr(c, a.sons[i], false) else: localError(c.config, n.sons[0].info, errXExpected, "tuple constructor") else: discard proc semYield(c: PContext, n: PNode): PNode = result = n checkSonsLen(n, 1, c.config) if c.p.owner == nil or c.p.owner.kind != skIterator: localError(c.config, n.info, errYieldNotAllowedHere) elif n.sons[0].kind != nkEmpty: n.sons[0] = semExprWithType(c, n.sons[0]) # check for type compatibility: var iterType = c.p.owner.typ let restype = iterType.sons[0] if restype != nil: if restype.kind != tyExpr: n.sons[0] = fitNode(c, restype, n.sons[0], n.info) if n.sons[0].typ == nil: internalError(c.config, n.info, "semYield") if resultTypeIsInferrable(restype): let inferred = n.sons[0].typ iterType.sons[0] = inferred semYieldVarResult(c, n, restype) else: localError(c.config, n.info, errCannotReturnExpr) elif c.p.owner.typ.sons[0] != nil: localError(c.config, n.info, errGenerated, "yield statement must yield a value") proc lookUpForDefined(c: PContext, i: PIdent, onlyCurrentScope: bool): PSym = if onlyCurrentScope: result = localSearchInScope(c, i) else: result = searchInScopes(c, i) # no need for stub loading proc lookUpForDefined(c: PContext, n: PNode, onlyCurrentScope: bool): PSym = case n.kind of nkIdent: result = lookUpForDefined(c, n.ident, onlyCurrentScope) of nkDotExpr: result = nil if onlyCurrentScope: return checkSonsLen(n, 2, c.config) var m = lookUpForDefined(c, n.sons[0], onlyCurrentScope) if m != nil and m.kind == skModule: let ident = considerQuotedIdent(c, n[1], n) if m == c.module: result = strTableGet(c.topLevelScope.symbols, ident) else: result = strTableGet(m.tab, ident) of nkAccQuoted: result = lookUpForDefined(c, considerQuotedIdent(c, n), onlyCurrentScope) of nkSym: result = n.sym of nkOpenSymChoice, nkClosedSymChoice: result = n.sons[0].sym else: localError(c.config, n.info, "identifier expected, but got: " & renderTree(n)) result = nil proc semDefined(c: PContext, n: PNode, onlyCurrentScope: bool): PNode = checkSonsLen(n, 2, c.config) # we replace this node by a 'true' or 'false' node: result = newIntNode(nkIntLit, 0) if not onlyCurrentScope and considerQuotedIdent(c, n[0], n).s == "defined": let d = considerQuotedIdent(c, n[1], n) result.intVal = ord isDefined(c.config, d.s) elif lookUpForDefined(c, n.sons[1], onlyCurrentScope) != nil: result.intVal = 1 result.info = n.info result.typ = getSysType(c.graph, n.info, tyBool) proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym = ## The argument to the proc should be nkCall(...) or similar ## Returns the macro/template symbol if isCallExpr(n): var expandedSym = qualifiedLookUp(c, n[0], {checkUndeclared}) if expandedSym == nil: errorUndeclaredIdentifier(c, n.info, n[0].renderTree) return errorSym(c, n[0]) if expandedSym.kind notin {skMacro, skTemplate}: localError(c.config, n.info, "'$1' is not a macro or template" % expandedSym.name.s) return errorSym(c, n[0]) result = expandedSym else: localError(c.config, n.info, "'$1' is not a macro or template" % n.renderTree) result = errorSym(c, n) proc expectString(c: PContext, n: PNode): string = var n = semConstExpr(c, n) if n.kind in nkStrKinds: return n.strVal else: localError(c.config, n.info, errStringLiteralExpected) proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo): PSym = result = newSym(kind, c.cache.idAnon, getCurrOwner(c), info) result.flags = {sfGenSym} proc semExpandToAst(c: PContext, n: PNode): PNode = let macroCall = n[1] when false: let expandedSym = expectMacroOrTemplateCall(c, macroCall) if expandedSym.kind == skError: return n macroCall.sons[0] = newSymNode(expandedSym, macroCall.info) markUsed(c.config, n.info, expandedSym, c.graph.usageSym) styleCheckUse(n.info, expandedSym) if isCallExpr(macroCall): for i in countup(1, macroCall.len-1): #if macroCall.sons[0].typ.sons[i].kind != tyExpr: macroCall.sons[i] = semExprWithType(c, macroCall[i], {}) # performing overloading resolution here produces too serious regressions: let headSymbol = macroCall[0] var cands = 0 var cand: PSym = nil var o: TOverloadIter var symx = initOverloadIter(o, c, headSymbol) while symx != nil: if symx.kind in {skTemplate, skMacro} and symx.typ.len == macroCall.len: cand = symx inc cands symx = nextOverloadIter(o, c, headSymbol) if cands == 0: localError(c.config, n.info, "expected a template that takes " & $(macroCall.len-1) & " arguments") elif cands >= 2: localError(c.config, n.info, "ambiguous symbol in 'getAst' context: " & $macroCall) else: let info = macroCall.sons[0].info macroCall.sons[0] = newSymNode(cand, info) markUsed(c.config, info, cand, c.graph.usageSym) styleCheckUse(info, cand) # we just perform overloading resolution here: #n.sons[1] = semOverloadedCall(c, macroCall, macroCall, {skTemplate, skMacro}) else: localError(c.config, n.info, "getAst takes a call, but got " & n.renderTree) # Preserve the magic symbol in order to be handled in evals.nim internalAssert c.config, n.sons[0].sym.magic == mExpandToAst #n.typ = getSysSym("NimNode").typ # expandedSym.getReturnType if n.kind == nkStmtList and n.len == 1: result = n[0] else: result = n result.typ = sysTypeFromName(c.graph, n.info, "NimNode") proc semExpandToAst(c: PContext, n: PNode, magicSym: PSym, flags: TExprFlags = {}): PNode = if sonsLen(n) == 2: n.sons[0] = newSymNode(magicSym, n.info) result = semExpandToAst(c, n) else: result = semDirectOp(c, n, flags) proc processQuotations(c: PContext; n: var PNode, op: string, quotes: var seq[PNode], ids: var seq[PNode]) = template returnQuote(q) = quotes.add q n = newIdentNode(getIdent(c.cache, $quotes.len), n.info) ids.add n return if n.kind == nkPrefix: checkSonsLen(n, 2, c.config) if n[0].kind == nkIdent: var examinedOp = n[0].ident.s if examinedOp == op: returnQuote n[1] elif examinedOp.startsWith(op): n.sons[0] = newIdentNode(getIdent(c.cache, examinedOp.substr(op.len)), n.info) elif n.kind == nkAccQuoted and op == "``": returnQuote n[0] for i in 0 ..< n.safeLen: processQuotations(c, n.sons[i], op, quotes, ids) proc semQuoteAst(c: PContext, n: PNode): PNode = internalAssert c.config, n.len == 2 or n.len == 3 # We transform the do block into a template with a param for # each interpolation. We'll pass this template to getAst. var quotedBlock = n[^1] op = if n.len == 3: expectString(c, n[1]) else: "``" quotes = newSeq[PNode](1) # the quotes will be added to a nkCall statement # leave some room for the callee symbol ids = newSeq[PNode]() # this will store the generated param names if quotedBlock.kind != nkStmtList: localError(c.config, n.info, errXExpected, "block") processQuotations(c, quotedBlock, op, quotes, ids) var dummyTemplate = newProcNode( nkTemplateDef, quotedBlock.info, body = quotedBlock, params = c.graph.emptyNode, name = newAnonSym(c, skTemplate, n.info).newSymNode, pattern = c.graph.emptyNode, genericParams = c.graph.emptyNode, pragmas = c.graph.emptyNode, exceptions = c.graph.emptyNode) if ids.len > 0: dummyTemplate.sons[paramsPos] = newNodeI(nkFormalParams, n.info) dummyTemplate[paramsPos].add getSysSym(c.graph, n.info, "typed").newSymNode # return type ids.add getSysSym(c.graph, n.info, "untyped").newSymNode # params type ids.add c.graph.emptyNode # no default value dummyTemplate[paramsPos].add newNode(nkIdentDefs, n.info, ids) var tmpl = semTemplateDef(c, dummyTemplate) quotes[0] = tmpl[namePos] result = newNode(nkCall, n.info, @[ createMagic(c.graph, "getAst", mExpandToAst).newSymNode, newNode(nkCall, n.info, quotes)]) result = semExpandToAst(c, result) proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # watch out, hacks ahead: let oldErrorCount = c.config.errorCounter let oldErrorMax = c.config.errorMax let oldCompilesId = c.compilesContextId inc c.compilesContextIdGenerator c.compilesContextId = c.compilesContextIdGenerator # do not halt after first error: c.config.errorMax = high(int) # open a scope for temporary symbol inclusions: let oldScope = c.currentScope openScope(c) let oldOwnerLen = len(c.graph.owners) let oldGenerics = c.generics let oldErrorOutputs = c.config.m.errorOutputs if efExplain notin flags: c.config.m.errorOutputs = {} let oldContextLen = msgs.getInfoContextLen(c.config) let oldInGenericContext = c.inGenericContext let oldInUnrolledContext = c.inUnrolledContext let oldInGenericInst = c.inGenericInst let oldInStaticContext = c.inStaticContext let oldProcCon = c.p c.generics = @[] var err: string try: result = semExpr(c, n, flags) if c.config.errorCounter != oldErrorCount: result = nil except ERecoverableError: discard # undo symbol table changes (as far as it's possible): c.compilesContextId = oldCompilesId c.generics = oldGenerics c.inGenericContext = oldInGenericContext c.inUnrolledContext = oldInUnrolledContext c.inGenericInst = oldInGenericInst c.inStaticContext = oldInStaticContext c.p = oldProcCon msgs.setInfoContextLen(c.config, oldContextLen) setLen(c.graph.owners, oldOwnerLen) c.currentScope = oldScope c.config.m.errorOutputs = oldErrorOutputs c.config.errorCounter = oldErrorCount c.config.errorMax = oldErrorMax proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode = # we replace this node by a 'true' or 'false' node: if sonsLen(n) != 2: return semDirectOp(c, n, flags) result = newIntNode(nkIntLit, ord(tryExpr(c, n[1], flags) != nil)) result.info = n.info result.typ = getSysType(c.graph, n.info, tyBool) proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode = if sonsLen(n) == 3: # XXX ugh this is really a hack: shallowCopy() can be overloaded only # with procs that take not 2 parameters: result = newNodeI(nkFastAsgn, n.info) result.add(n[1]) result.add(n[2]) result = semAsgn(c, result) else: result = semDirectOp(c, n, flags) proc createFlowVar(c: PContext; t: PType; info: TLineInfo): PType = result = newType(tyGenericInvocation, c.module) addSonSkipIntLit(result, magicsys.getCompilerProc(c.graph, "FlowVar").typ) addSonSkipIntLit(result, t) result = instGenericContainer(c, info, result, allowMetaTypes = false) proc instantiateCreateFlowVarCall(c: PContext; t: PType; info: TLineInfo): PSym = let sym = magicsys.getCompilerProc(c.graph, "nimCreateFlowVar") if sym == nil: localError(c.config, info, "system needs: nimCreateFlowVar") var bindings: TIdTable initIdTable(bindings) bindings.idTablePut(sym.ast[genericParamsPos].sons[0].typ, t) result = c.semGenerateInstance(c, sym, bindings, info) # since it's an instantiation, we unmark it as a compilerproc. Otherwise # codegen would fail: if sfCompilerProc in result.flags: result.flags = result.flags - {sfCompilerProc, sfExportC, sfImportC} result.loc.r = nil proc setMs(n: PNode, s: PSym): PNode = result = n n.sons[0] = newSymNode(s) n.sons[0].info = n.info proc extractImports(n: PNode; result: PNode) = if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}: result.add copyTree(n) n.kind = nkEmpty return for i in 0..<n.safeLen: extractImports(n[i], result) proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = # this is a hotspot in the compiler! # DON'T forget to update ast.SpecialSemMagics if you add a magic here! result = n case s.magic # magics that need special treatment of mAddr: checkSonsLen(n, 2, c.config) result = semAddr(c, n.sons[1], s.name.s == "unsafeAddr") of mTypeOf: checkSonsLen(n, 2, c.config) result = semTypeOf(c, n.sons[1]) #of mArrGet: result = semArrGet(c, n, flags) #of mArrPut: result = semArrPut(c, n, flags) #of mAsgn: result = semAsgnOpr(c, n) of mDefined: result = semDefined(c, setMs(n, s), false) of mDefinedInScope: result = semDefined(c, setMs(n, s), true) of mCompiles: result = semCompiles(c, setMs(n, s), flags) #of mLow: result = semLowHigh(c, setMs(n, s), mLow) #of mHigh: result = semLowHigh(c, setMs(n, s), mHigh) of mSizeOf: result = semSizeof(c, setMs(n, s)) of mIs: result = semIs(c, setMs(n, s), flags) #of mOf: result = semOf(c, setMs(n, s)) of mShallowCopy: result = semShallowCopy(c, n, flags) of mExpandToAst: result = semExpandToAst(c, n, s, flags) of mQuoteAst: result = semQuoteAst(c, n) of mAstToStr: checkSonsLen(n, 2, c.config) result = newStrNodeT(renderTree(n[1], {renderNoComments}), n, c.graph) result.typ = getSysType(c.graph, n.info, tyString) of mParallel: if parallel notin c.features: localError(c.config, n.info, "use the {.experimental.} pragma to enable 'parallel'") result = setMs(n, s) var x = n.lastSon if x.kind == nkDo: x = x.sons[bodyPos] inc c.inParallelStmt result.sons[1] = semStmt(c, x) dec c.inParallelStmt of mSpawn: result = setMs(n, s) for i in 1 ..< n.len: result.sons[i] = semExpr(c, n.sons[i]) let typ = result[^1].typ if not typ.isEmptyType: if spawnResult(typ, c.inParallelStmt > 0) == srFlowVar: result.typ = createFlowVar(c, typ, n.info) else: result.typ = typ result.add instantiateCreateFlowVarCall(c, typ, n.info).newSymNode else: result.add c.graph.emptyNode of mProcCall: result = setMs(n, s) result.sons[1] = semExpr(c, n.sons[1]) result.typ = n[1].typ of mPlugin: # semDirectOp with conditional 'afterCallActions': let nOrig = n.copyTree #semLazyOpAux(c, n) result = semOverloadedCallAnalyseEffects(c, n, nOrig, flags) if result == nil: result = errorNode(c, n) else: let callee = result.sons[0].sym if callee.magic == mNone: semFinishOperands(c, result) activate(c, result) fixAbstractType(c, result) analyseIfAddressTakenInCall(c, result) if callee.magic != mNone: result = magicsAfterOverloadResolution(c, result, flags) of mRunnableExamples: if c.config.cmd == cmdDoc and n.len >= 2 and n.lastSon.kind == nkStmtList: if sfMainModule in c.module.flags: let inp = toFullPath(c.config, c.module.info) if c.runnableExamples == nil: c.runnableExamples = newTree(nkStmtList, newTree(nkImportStmt, newStrNode(nkStrLit, expandFilename(inp)))) let imports = newTree(nkStmtList) extractImports(n.lastSon, imports) for imp in imports: c.runnableExamples.add imp c.runnableExamples.add newTree(nkBlockStmt, c.graph.emptyNode, copyTree n.lastSon) result = setMs(n, s) else: result = c.graph.emptyNode else: result = semDirectOp(c, n, flags) proc semWhen(c: PContext, n: PNode, semCheck = true): PNode = # If semCheck is set to false, ``when`` will return the verbatim AST of # the correct branch. Otherwise the AST will be passed through semStmt. result = nil template setResult(e: untyped) = if semCheck: result = semExpr(c, e) # do not open a new scope! else: result = e # Check if the node is "when nimvm" # when nimvm: # ... # else: # ... var whenNimvm = false var typ = commonTypeBegin if n.sons.len == 2 and n.sons[0].kind == nkElifBranch and n.sons[1].kind == nkElse: let exprNode = n.sons[0].sons[0] if exprNode.kind == nkIdent: whenNimvm = lookUp(c, exprNode).magic == mNimvm elif exprNode.kind == nkSym: whenNimvm = exprNode.sym.magic == mNimvm if whenNimvm: n.flags.incl nfLL for i in countup(0, sonsLen(n) - 1): var it = n.sons[i] case it.kind of nkElifBranch, nkElifExpr: checkSonsLen(it, 2, c.config) if whenNimvm: if semCheck: it.sons[1] = semExpr(c, it.sons[1]) typ = commonType(typ, it.sons[1].typ) result = n # when nimvm is not elimited until codegen else: var e = semConstExpr(c, it.sons[0]) if e.kind != nkIntLit: # can happen for cascading errors, assume false # InternalError(n.info, "semWhen") discard elif e.intVal != 0 and result == nil: setResult(it.sons[1]) of nkElse, nkElseExpr: checkSonsLen(it, 1, c.config) if result == nil or whenNimvm: if semCheck: it.sons[0] = semExpr(c, it.sons[0]) typ = commonType(typ, it.sons[0].typ) if result == nil: result = it.sons[0] else: illFormedAst(n, c.config) if result == nil: result = newNodeI(nkEmpty, n.info) if whenNimvm: result.typ = typ # The ``when`` statement implements the mechanism for platform dependent # code. Thus we try to ensure here consistent ID allocation after the # ``when`` statement. idSynchronizationPoint(200) proc semSetConstr(c: PContext, n: PNode): PNode = result = newNodeI(nkCurly, n.info) result.typ = newTypeS(tySet, c) if sonsLen(n) == 0: rawAddSon(result.typ, newTypeS(tyEmpty, c)) else: # only semantic checking for all elements, later type checking: var typ: PType = nil for i in countup(0, sonsLen(n) - 1): if isRange(n.sons[i]): checkSonsLen(n.sons[i], 3, c.config) n.sons[i].sons[1] = semExprWithType(c, n.sons[i].sons[1]) n.sons[i].sons[2] = semExprWithType(c, n.sons[i].sons[2]) if typ == nil: typ = skipTypes(n.sons[i].sons[1].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) n.sons[i].typ = n.sons[i].sons[2].typ # range node needs type too elif n.sons[i].kind == nkRange: # already semchecked if typ == nil: typ = skipTypes(n.sons[i].sons[0].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) else: n.sons[i] = semExprWithType(c, n.sons[i]) if typ == nil: typ = skipTypes(n.sons[i].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) if not isOrdinalType(typ): localError(c.config, n.info, errOrdinalTypeExpected) typ = makeRangeType(c, 0, MaxSetElements-1, n.info) elif lengthOrd(c.config, typ) > MaxSetElements: typ = makeRangeType(c, 0, MaxSetElements-1, n.info) addSonSkipIntLit(result.typ, typ) for i in countup(0, sonsLen(n) - 1): var m: PNode let info = n.sons[i].info if isRange(n.sons[i]): m = newNodeI(nkRange, info) addSon(m, fitNode(c, typ, n.sons[i].sons[1], info)) addSon(m, fitNode(c, typ, n.sons[i].sons[2], info)) elif n.sons[i].kind == nkRange: m = n.sons[i] # already semchecked else: m = fitNode(c, typ, n.sons[i], info) addSon(result, m) proc semTableConstr(c: PContext, n: PNode): PNode = # we simply transform ``{key: value, key2, key3: value}`` to # ``[(key, value), (key2, value2), (key3, value2)]`` result = newNodeI(nkBracket, n.info) var lastKey = 0 for i in 0..n.len-1: var x = n.sons[i] if x.kind == nkExprColonExpr and sonsLen(x) == 2: for j in countup(lastKey, i-1): var pair = newNodeI(nkTupleConstr, x.info) pair.add(n.sons[j]) pair.add(x[1]) result.add(pair) var pair = newNodeI(nkTupleConstr, x.info) pair.add(x[0]) pair.add(x[1]) result.add(pair) lastKey = i+1 if lastKey != n.len: illFormedAst(n, c.config) result = semExpr(c, result) type TParKind = enum paNone, paSingle, paTupleFields, paTuplePositions proc checkPar(c: PContext; n: PNode): TParKind = var length = sonsLen(n) if length == 0: result = paTuplePositions # () elif length == 1: if n.sons[0].kind == nkExprColonExpr: result = paTupleFields elif n.kind == nkTupleConstr: result = paTuplePositions else: result = paSingle # (expr) else: if n.sons[0].kind == nkExprColonExpr: result = paTupleFields else: result = paTuplePositions for i in countup(0, length - 1): if result == paTupleFields: if (n.sons[i].kind != nkExprColonExpr) or not (n.sons[i].sons[0].kind in {nkSym, nkIdent}): localError(c.config, n.sons[i].info, errNamedExprExpected) return paNone else: if n.sons[i].kind == nkExprColonExpr: localError(c.config, n.sons[i].info, errNamedExprNotAllowed) return paNone proc semTupleFieldsConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = result = newNodeI(nkTupleConstr, n.info) var typ = newTypeS(tyTuple, c) typ.n = newNodeI(nkRecList, n.info) # nkIdentDefs var ids = initIntSet() for i in countup(0, sonsLen(n) - 1): if n[i].kind != nkExprColonExpr or n[i][0].kind notin {nkSym, nkIdent}: illFormedAst(n.sons[i], c.config) var id: PIdent if n.sons[i].sons[0].kind == nkIdent: id = n.sons[i].sons[0].ident else: id = n.sons[i].sons[0].sym.name if containsOrIncl(ids, id.id): localError(c.config, n.sons[i].info, errFieldInitTwice % id.s) n.sons[i].sons[1] = semExprWithType(c, n.sons[i].sons[1], flags*{efAllowDestructor}) var f = newSymS(skField, n.sons[i].sons[0], c) f.typ = skipIntLit(n.sons[i].sons[1].typ) f.position = i rawAddSon(typ, f.typ) addSon(typ.n, newSymNode(f)) n.sons[i].sons[0] = newSymNode(f) addSon(result, n.sons[i]) result.typ = typ proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = result = n # we don't modify n, but compute the type: result.kind = nkTupleConstr var typ = newTypeS(tyTuple, c) # leave typ.n nil! for i in countup(0, sonsLen(n) - 1): n.sons[i] = semExprWithType(c, n.sons[i], flags*{efAllowDestructor}) addSonSkipIntLit(typ, n.sons[i].typ) result.typ = typ proc isTupleType(n: PNode): bool = if n.len == 0: return false # don't interpret () as type for i in countup(0, n.len - 1): if n[i].typ == nil or n[i].typ.kind != tyTypeDesc: return false return true include semobjconstr proc semBlock(c: PContext, n: PNode): PNode = result = n inc(c.p.nestedBlockCounter) checkSonsLen(n, 2, c.config) openScope(c) # BUGFIX: label is in the scope of block! if n.sons[0].kind != nkEmpty: var labl = newSymG(skLabel, n.sons[0], c) if sfGenSym notin labl.flags: addDecl(c, labl) n.sons[0] = newSymNode(labl, n.sons[0].info) suggestSym(c.config, n.sons[0].info, labl, c.graph.usageSym) styleCheckDef(c.config, labl) n.sons[1] = semExpr(c, n.sons[1]) n.typ = n.sons[1].typ if isEmptyType(n.typ): n.kind = nkBlockStmt else: n.kind = nkBlockExpr closeScope(c) dec(c.p.nestedBlockCounter) proc semExportExcept(c: PContext, n: PNode): PNode = let moduleName = semExpr(c, n[0]) if moduleName.kind != nkSym or moduleName.sym.kind != skModule: localError(c.config, n.info, "The export/except syntax expects a module name") return n let exceptSet = readExceptSet(c, n) let exported = moduleName.sym strTableAdd(c.module.tab, exported) var i: TTabIter var s = initTabIter(i, exported.tab) while s != nil: if s.kind in ExportableSymKinds+{skModule} and s.name.id notin exceptSet: strTableAdd(c.module.tab, s) s = nextIter(i, exported.tab) result = n proc semExport(c: PContext, n: PNode): PNode = var x = newNodeI(n.kind, n.info) for i in 0..<n.len: let a = n.sons[i] var o: TOverloadIter var s = initOverloadIter(o, c, a) if s == nil: localError(c.config, a.info, errGenerated, "cannot export: " & renderTree(a)) elif s.kind == skModule: # forward everything from that module: strTableAdd(c.module.tab, s) x.add(newSymNode(s, a.info)) var ti: TTabIter var it = initTabIter(ti, s.tab) while it != nil: if it.kind in ExportableSymKinds+{skModule}: strTableAdd(c.module.tab, it) it = nextIter(ti, s.tab) else: while s != nil: if s.kind in ExportableSymKinds+{skModule}: x.add(newSymNode(s, a.info)) strTableAdd(c.module.tab, s) s = nextOverloadIter(o, c, a) result = n proc shouldBeBracketExpr(n: PNode): bool = assert n.kind in nkCallKinds let a = n.sons[0] if a.kind in nkCallKinds: let b = a[0] if b.kind in nkSymChoices: for i in 0..<b.len: if b[i].kind == nkSym and b[i].sym.magic == mArrGet: let be = newNodeI(nkBracketExpr, n.info) for i in 1..<a.len: be.add(a[i]) n.sons[0] = be return true proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result = n if c.config.cmd == cmdIdeTools: suggestExpr(c, n) if nfSem in n.flags: return case n.kind of nkIdent, nkAccQuoted: let checks = if efNoEvaluateGeneric in flags: {checkUndeclared, checkPureEnumFields} elif efInCall in flags: {checkUndeclared, checkModule, checkPureEnumFields} else: {checkUndeclared, checkModule, checkAmbiguity, checkPureEnumFields} var s = qualifiedLookUp(c, n, checks) if c.matchedConcept == nil: semCaptureSym(s, c.p.owner) result = semSym(c, n, s, flags) if s.kind in {skProc, skFunc, skMethod, skConverter, skIterator}: #performProcvarCheck(c, n, s) result = symChoice(c, n, s, scClosed) if result.kind == nkSym: markIndirect(c, result.sym) # if isGenericRoutine(result.sym): # localError(c.config, n.info, errInstantiateXExplicitly, s.name.s) of nkSym: # because of the changed symbol binding, this does not mean that we # don't have to check the symbol for semantics here again! result = semSym(c, n, n.sym, flags) of nkEmpty, nkNone, nkCommentStmt, nkType: discard of nkNilLit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyNil) of nkIntLit: if result.typ == nil: setIntLitType(c.graph, result) of nkInt8Lit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt8) of nkInt16Lit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt16) of nkInt32Lit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt32) of nkInt64Lit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt64) of nkUIntLit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt) of nkUInt8Lit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt8) of nkUInt16Lit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt16) of nkUInt32Lit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt32) of nkUInt64Lit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt64) #of nkFloatLit: # if result.typ == nil: result.typ = getFloatLitType(result) of nkFloat32Lit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyFloat32) of nkFloat64Lit, nkFloatLit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyFloat64) of nkFloat128Lit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyFloat128) of nkStrLit..nkTripleStrLit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyString) of nkCharLit: if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyChar) of nkDotExpr: result = semFieldAccess(c, n, flags) if result.kind == nkDotCall: result.kind = nkCall result = semExpr(c, result, flags) of nkBind: message(c.config, n.info, warnDeprecated, "bind") result = semExpr(c, n.sons[0], flags) of nkTypeOfExpr, nkTupleTy, nkTupleClassTy, nkRefTy..nkEnumTy, nkStaticTy: if c.matchedConcept != nil and n.len == 1: let modifier = n.modifierTypeKindOfNode if modifier != tyNone: var baseType = semExpr(c, n[0]).typ.skipTypes({tyTypeDesc}) result.typ = c.makeTypeDesc(c.newTypeWithSons(modifier, @[baseType])) return var typ = semTypeNode(c, n, nil).skipTypes({tyTypeDesc}) result.typ = makeTypeDesc(c, typ) of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: # check if it is an expression macro: checkMinSonsLen(n, 1, c.config) #when defined(nimsuggest): # if gIdeCmd == ideCon and c.config.m.trackPos == n.info: suggestExprNoCheck(c, n) let mode = if nfDotField in n.flags: {} else: {checkUndeclared} var s = qualifiedLookUp(c, n.sons[0], mode) if s != nil: #if c.config.cmd == cmdPretty and n.sons[0].kind == nkDotExpr: # pretty.checkUse(n.sons[0].sons[1].info, s) case s.kind of skMacro: if sfImmediate notin s.flags: result = semDirectOp(c, n, flags) else: result = semMacroExpr(c, n, n, s, flags) of skTemplate: if sfImmediate notin s.flags: result = semDirectOp(c, n, flags) else: result = semTemplateExpr(c, n, s, flags) of skType: # XXX think about this more (``set`` procs) if n.len == 2: result = semConv(c, n) elif contains(c.ambiguousSymbols, s.id) and n.len == 1: errorUseQualifier(c, n.info, s) elif n.len == 1: result = semObjConstr(c, n, flags) elif s.magic == mNone: result = semDirectOp(c, n, flags) else: result = semMagic(c, n, s, flags) of skProc, skFunc, skMethod, skConverter, skIterator: if s.magic == mNone: result = semDirectOp(c, n, flags) else: result = semMagic(c, n, s, flags) else: #liMessage(n.info, warnUser, renderTree(n)); result = semIndirectOp(c, n, flags) elif (n[0].kind == nkBracketExpr or shouldBeBracketExpr(n)) and isSymChoice(n[0][0]): # indirectOp can deal with explicit instantiations; the fixes # the 'newSeq[T](x)' bug setGenericParams(c, n.sons[0]) result = semDirectOp(c, n, flags) elif isSymChoice(n.sons[0]) or nfDotField in n.flags: result = semDirectOp(c, n, flags) else: result = semIndirectOp(c, n, flags) of nkWhen: if efWantStmt in flags: result = semWhen(c, n, true) else: result = semWhen(c, n, false) if result == n: # This is a "when nimvm" stmt. result = semWhen(c, n, true) else: result = semExpr(c, result, flags) of nkBracketExpr: checkMinSonsLen(n, 1, c.config) result = semArrayAccess(c, n, flags) of nkCurlyExpr: result = semExpr(c, buildOverloadedSubscripts(n, getIdent(c.cache, "{}")), flags) of nkPragmaExpr: var expr = n[0] pragma = n[1] pragmaName = considerQuotedIdent(c, pragma[0]) flags = flags case whichKeyword(pragmaName) of wExplain: flags.incl efExplain else: # what other pragmas are allowed for expressions? `likely`, `unlikely` invalidPragma(c, n) result = semExpr(c, n[0], flags) of nkPar, nkTupleConstr: case checkPar(c, n) of paNone: result = errorNode(c, n) of paTuplePositions: var tupexp = semTuplePositionsConstr(c, n, flags) if isTupleType(tupexp): # reinterpret as type var typ = semTypeNode(c, n, nil).skipTypes({tyTypeDesc}) result.typ = makeTypeDesc(c, typ) else: result = tupexp of paTupleFields: result = semTupleFieldsConstr(c, n, flags) of paSingle: result = semExpr(c, n.sons[0], flags) of nkCurly: result = semSetConstr(c, n) of nkBracket: result = semArrayConstr(c, n, flags) of nkObjConstr: result = semObjConstr(c, n, flags) of nkLambdaKinds: result = semLambda(c, n, flags) of nkDerefExpr: result = semDeref(c, n) of nkAddr: result = n checkSonsLen(n, 1, c.config) result = semAddr(c, n.sons[0]) of nkHiddenAddr, nkHiddenDeref: checkSonsLen(n, 1, c.config) n.sons[0] = semExpr(c, n.sons[0], flags) of nkCast: result = semCast(c, n) of nkIfExpr, nkIfStmt: result = semIf(c, n) of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkHiddenCallConv: checkSonsLen(n, 2, c.config) considerGenSyms(c, n) of nkStringToCString, nkCStringToString, nkObjDownConv, nkObjUpConv: checkSonsLen(n, 1, c.config) considerGenSyms(c, n) of nkChckRangeF, nkChckRange64, nkChckRange: checkSonsLen(n, 3, c.config) considerGenSyms(c, n) of nkCheckedFieldExpr: checkMinSonsLen(n, 2, c.config) considerGenSyms(c, n) of nkTableConstr: result = semTableConstr(c, n) of nkClosedSymChoice, nkOpenSymChoice: # handling of sym choices is context dependent # the node is left intact for now discard of nkStaticExpr: result = semStaticExpr(c, n[0]) of nkAsgn: result = semAsgn(c, n) of nkBlockStmt, nkBlockExpr: result = semBlock(c, n) of nkStmtList, nkStmtListExpr: result = semStmtList(c, n, flags) of nkRaiseStmt: result = semRaise(c, n) of nkVarSection: result = semVarOrLet(c, n, skVar) of nkLetSection: result = semVarOrLet(c, n, skLet) of nkConstSection: result = semConst(c, n) of nkTypeSection: result = semTypeSection(c, n) of nkDiscardStmt: result = semDiscard(c, n) of nkWhileStmt: result = semWhile(c, n) of nkTryStmt: result = semTry(c, n) of nkBreakStmt, nkContinueStmt: result = semBreakOrContinue(c, n) of nkForStmt, nkParForStmt: result = semFor(c, n) of nkCaseStmt: result = semCase(c, n) of nkReturnStmt: result = semReturn(c, n) of nkUsingStmt: result = semUsing(c, n) of nkAsmStmt: result = semAsm(c, n) of nkYieldStmt: result = semYield(c, n) of nkPragma: pragma(c, c.p.owner, n, stmtPragmas) of nkIteratorDef: result = semIterator(c, n) of nkProcDef: result = semProc(c, n) of nkFuncDef: result = semFunc(c, n) of nkMethodDef: result = semMethod(c, n) of nkConverterDef: result = semConverterDef(c, n) of nkMacroDef: result = semMacroDef(c, n) of nkTemplateDef: result = semTemplateDef(c, n) of nkImportStmt: # this particular way allows 'import' in a 'compiles' context so that # template canImport(x): bool = # compiles: # import x # # works: if c.currentScope.depthLevel > 2 + c.compilesContextId: localError(c.config, n.info, errXOnlyAtModuleScope % "import") result = evalImport(c, n) of nkImportExceptStmt: if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "import") result = evalImportExcept(c, n) of nkFromStmt: if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "from") result = evalFrom(c, n) of nkIncludeStmt: #if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "include") result = evalInclude(c, n) of nkExportStmt: if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "export") result = semExport(c, n) of nkExportExceptStmt: if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "export") result = semExportExcept(c, n) of nkPragmaBlock: result = semPragmaBlock(c, n) of nkStaticStmt: result = semStaticStmt(c, n) of nkDefer: n.sons[0] = semExpr(c, n.sons[0]) if not n.sons[0].typ.isEmptyType and not implicitlyDiscardable(n.sons[0]): localError(c.config, n.info, "'defer' takes a 'void' expression") #localError(c.config, n.info, errGenerated, "'defer' not allowed in this context") of nkGotoState, nkState: if n.len != 1 and n.len != 2: illFormedAst(n, c.config) for i in 0 ..< n.len: n.sons[i] = semExpr(c, n.sons[i]) of nkComesFrom: discard "ignore the comes from information for now" else: localError(c.config, n.info, "invalid expression: " & renderTree(n, {renderNoComments})) if result != nil: incl(result.flags, nfSem)