nimskull icon indicating copy to clipboard operation
nimskull copied to clipboard

language design - `compileTime` pragma: symbol visibility, types, and transfering procedural values

Open saem opened this issue 1 year ago • 0 comments

Goal

Determine a more precise meaning for the compileTime pragma, and relevant interactions, and then make the compiler do that. As a first step document what should be true now, then we can work backwards.

Illustrative Examples

With the latest rework of compileTime semantics (https://github.com/nim-works/nimskull/pull/580), which will be merged shortly, the following no longer works as inner with the proc pis inferred as acompiletimedeclaration, which cascades throughconst`.

type Proc = proc()

proc p(): Proc {.compileTime.} =
  proc inner() = # inferred to be compile-time only
    discard

  result = inner

const c = p()

let rt = c # <- fails to compile now
rt()

Here is a varint using a macro:

macro m() =
  proc p() = # inferred to be compile-time only
    discard

  result = quote do:
    `p`()

m() # the generated AST fails to compile

Current State of Understanding

Primarily using the above first example of two examples.

The present working conceptual model is:

  • compileTime is applied to the symbol and controls visibility
  • p is returning a procedural value, meaning that it's not returning inner but the value inner holds, inner can be thought of as const inner: proc() = proc () = discard

NB: if a procedural value from inner is returned from a compiletime ("chunk of code", p) and if that returned procedural value raised, then it's a little surprising to see a runtime trace from ostensibly "compiletime" section of code. This is considered an entirely acceptable tradeoff, because disallowing conversion of compiletime procedural values to runtime would be a huge miss in terms of providing tools that do type or 'closed' data driven definitional emit, which is yet another tool to employ prior to resorting to macros.

Related conceptual items:

  • NimNode parameters have special handling which promote routines taking NimNode args to compiletime
    • this is done at the symbol level, but should probably be at the type level or both?

Thought exercises:

  • special case NimNode has a stage restriction (comptime) and cascade affect (promotes the proc symbol to compiletime)
    • here the type NimNode is compiletime (not the symbol, because that would mean aliases or distincts could break this)
  • referring to symbols in the body of p from within inner would mean it closes over them:
    • if those are definitely compiletime symbols, then does that mean we snapshot the closed values and proceed?
      • would this snapshot? a must for var locals and/or const/let locals with defs that vary per invocation
    • or, promote the procedural value returned to compiletime?
  • for compiletime types (NimNode) passed into generic parameter positions, how does that impact compileTime-ness of things?

Thanks to @zerbina for the example, and @zerbina and @Clyybber for the debate thus far.

saem avatar Mar 14 '23 22:03 saem