You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

109 lines
5.4 KiB
Plaintext

need to use regular functions for `rec {}` getters but arrow functions for `{}` getters, to ensure that using `this` for property access works correctly
ADD TO TESTS:
let a = 0; in rec { a = 1; foo = bar: a * 2 }
# 2
let a = 0; in rec { a = 1; foo = let func = bar: a * 2; in rec { a = 3; baz = func; }; }
# 2
----
Things that could inhibit evaluation of a thunk:
- Conditionals (already handled by JS semantics)
lazy-wrapping expressions which are object/attrset properties, array/list elements, function args, bindings(!), and so on (and unpacking them on the corresponding accesses) but otherwise just directly translating to JS
Note: equality comparison for lists and attrsets is a *deep* comparison by default! Need an equality operator wrapper to handle this. For derivations, equality is determined by the outPath. Functions are never equal, and the only type compatibility is between integers and floats.
----
General wrapping concept: expressions are wrapped in a lazy eval wrapper when they are either bindings or members of a complex data structure, where evaluating the data structure itself *does not* imply evaluating its members. The "set of function args in a function call" counts as a complex data structure.
Binding access can be blindly unwrapped because regardless of whether it's a local binding or eg. originates from a `with` statement (that takes an object), the binding itself will always contain a wrapped expression.
Passing around wrapped unevaluated expressions (eg. assigning an object property to *another* object property, or passing an object property into a function call) is made possible in a somewhat hacky way; instead of first unwrapping (evaluating) the source expression and then passing the result somewhere, the evaluation *itself* will (according to the above principles) be wrapped in a lazy wrapper, meaning that the evaluation of the source expression will only take place if the *new* wrapper is evaluated somewhere.
This means that you end up with something along the lines of `someFunc(() => otherWrapper())` - the immediate call from within a wrapper might *seem* redundant, but therefore isn't. It may be possible to improve on this in the future, but for now this significantly reduces compiler complexity, because the compiler does not need to track provenance of the source expression to determine when it can be passed in directly as-is, and does not need to do code analysis to determine whether it is safe to do so in all cases. Let's just let V8 worry about optimizing this for now.
Language elements:
- Number literal
- String literal
- Path literal
- URL literal
- Object literal (wrap object property values)
- Object literal, recursive (wrap object property values)
- Object literal, inherits (DO NOT wrap/unwrap, all inheritable values are already wrapped)
- Array literal (wrap array elements)
- let..in bindings (wrap binding values)
- Function definition (DO NOT wrap or unwrap args, this is handled at call/access sites)
- Function definition, @-all arg (DO NOT wrap/unwrap, all arguments are already wrapped, just convert to object literal)
- Binding reference (unwrap)
- Assert
- Conditional, if/then/else (DO NOT do extra wrapping/unwrapping, JS semantics take care of the conditional unwrapping here)
- `with` expression (DO NOT unwrap, just apply the properties as bindings directly in their already-wrapped form)
- Binop: Access property, exists (unwrap)
- Binop: Access property, does not exist (fatal)
- Binop: Access property with fallback, does not exist (DO NOT wrap/unwrap, this is just a conditional!)
- Binop: Call function (wrap arg expressions) -- note: SHOULD NOT evaluate inputs to those expressions at call time, see example below
- Binop: Arithmetic negate
- Binop: Has attribute (unwrap object properties for all path segments except the final one)
- Binop: Concatenate array (DO NOT unwrap array elements, concat them 'blindly')
- Binop: Multiply
- Binop: Divide
- Binop: Add
- Binop: Subtract
- Binop: Concatenate string
- Binop: Boolean negate
- Binop: Merge objects (DO NOT unwrap properties, merge them 'blindly')
- Binop: Less than
- Binop: Less than or equal to
- Binop: More than
- Binop: More than or equal to
- Binop: Is equal
- Binop: Is not equal
- Binop: Logical AND
- Binop: Logical OR
- Binop: Logical implication
Things that wrap:
- Object literal, explicit property values only, NOT inherited properties
- Array literal, elements
- Scope bindings (definitions)
- Function call arguments, named/single arguments only, NOT members of catch-all @
Things that unwrap:
- Binding access
- Object property access (including every property in a `has attribute` except for the last one)
- Array element access
////////
let arg = { a: trace "foo" 42 }
let func = stuff: trace "called" true
func trace "pass" arg.a
nix-repl> let arg = { a = builtins.trace "foo" 42; }; func = stuff: (builtins.trace "called" true); in func (builtins.trace "pass" arg.a)
trace: called
true
nix-repl> let arg = { a = builtins.trace "foo" 42; }; func = stuff: (builtins.trace "called" stuff); in func (builtins.trace "pass" arg.a)
trace: called
trace: pass
trace: foo
42
//////
maybe cases
rec { a = 1; b = 2; c = { inherit a; }; }.c
let a = "foo"; b = a; in { "${a}" = 1; "${b}" = 2; }
let a = "foo"; in { "${a}" = 1; "${a}" = 2; }
let a = "foo"; in { "${foo}" = 1; "${foo}" = 2; }
nix-repl> let a = "foo"; ${a} = "bar"; in true
error: dynamic attributes not allowed in let at (string):1:1