Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use Logging; | |
| 3 | ||
| 4 | use Source.LOCATION; | |
| 5 | ||
| 6 | // Enforces pure function-typed slots: a value reaching one must be | |
| 7 | // a store-free literal, a store-free or declared-pure named | |
| 8 | // function, or a value whose own static type is already pure. | |
| 9 | // | |
| 10 | // The test is "cannot prove store-free", not "is known to store", | |
| 11 | // so it is conservative and reports a warning. An unsatisfied | |
| 12 | // argument also drops heap facts at the call; a store does not, | |
| 13 | // so for a store the warning is the only signal. | |
| 14 | class PURE_SLOT_CHECK is | |
| 15 | _logger: Logger; | |
| 16 | _flow: NARROWING_FLOW; | |
| 17 | _build_flags: Compiler.GLOBAL_BUILD_FLAGS; | |
| 18 | ||
| 19 | init( | |
| 20 | logger: Logger, | |
| 21 | flow: NARROWING_FLOW, | |
| 22 | build_flags: Compiler.GLOBAL_BUILD_FLAGS | |
| 23 | ) is | |
| 24 | super.init(); | |
| 25 | ||
| 26 | _logger = logger; | |
| 27 | _flow = flow; | |
| 28 | _build_flags = build_flags; | |
| 29 | si | |
| 30 | ||
| 31 | // Every argument of a fully-built call that lands in a pure | |
| 32 | // formal. Applies to any call-shaped value: an ordinary call, | |
| 33 | // a user-defined operator or indexer, a construction. | |
| 34 | check_call(location: LOCATION, value: IR.Values.Value?) is | |
| 35 | let function: Semantic.Symbols.Function? mut = null; | |
| 36 | let arguments: Collections.List[IR.Values.Value]? mut = null; | |
| 37 | ||
| 38 | if isa IR.Values.Call.INSTANCE(value) then | |
| 39 | let call = cast IR.Values.Call.INSTANCE(value); | |
| 40 | function = call.function; | |
| 41 | arguments = call.arguments; | |
| 42 | elif isa IR.Values.Call.GLOBAL(value) then | |
| 43 | let call = cast IR.Values.Call.GLOBAL(value); | |
| 44 | function = call.function; | |
| 45 | arguments = call.arguments; | |
| 46 | elif isa IR.Values.Call.STATIC(value) then | |
| 47 | let call = cast IR.Values.Call.STATIC(value); | |
| 48 | function = call.function; | |
| 49 | arguments = call.arguments; | |
| 50 | elif isa IR.Values.Call.STRUCT(value) then | |
| 51 | let call = cast IR.Values.Call.STRUCT(value); | |
| 52 | function = call.function; | |
| 53 | arguments = call.arguments; | |
| 54 | else | |
| 55 | return; | |
| 56 | fi | |
| 57 | ||
| 58 | let formals = function.arguments; | |
| 59 | ||
| 60 | for i in 0..formals.count do | |
| 61 | if i >= arguments.count then | |
| 62 | return; | |
| 63 | fi | |
| 64 | ||
| 65 | if formals[i].is_pure_function /\ !is_satisfied(arguments[i]) then | |
| 66 | if !_build_flags.no_warn_impure_function_value then | |
| 67 | _logger.warn(location, "impure-function-value", "argument must be a pure function"); | |
| 68 | fi | |
| 69 | ||
| 70 | _flow.on_call(location); | |
| 71 | fi | |
| 72 | od | |
| 73 | si | |
| 74 | ||
| 75 | // A value stored into a slot whose type is a pure function | |
| 76 | // type. The slot's type is a claim that it holds store-free | |
| 77 | // values, and is trusted wherever the slot is read, so the | |
| 78 | // store is where the value has to be shown store-free. | |
| 79 | check_store(location: LOCATION, target_type: Semantic.Types.Type?, value: IR.Values.Value?) is | |
| 80 | if | |
| 81 | target_type? /\ target_type.is_pure_function /\ | |
| 82 | !is_satisfied(value) /\ | |
| 83 | !_build_flags.no_warn_impure_function_value | |
| 84 | then | |
| 85 | _logger.warn(location, "impure-function-value", "value must be a pure function"); | |
| 86 | fi | |
| 87 | si | |
| 88 | ||
| 89 | is_satisfied(argument: IR.Values.Value?) -> bool is | |
| 90 | if !argument? then | |
| 91 | return true; | |
| 92 | fi | |
| 93 | ||
| 94 | // A reference is typed with the type of the slot it is | |
| 95 | // read into, so where it names a known function that | |
| 96 | // function's own store-free bit decides. | |
| 97 | if let function = argument.referenced_function then | |
| 98 | return _is_store_free(function); | |
| 99 | fi | |
| 100 | ||
| 101 | if argument.has_symbol then | |
| 102 | let symbol = argument.symbol; | |
| 103 | ||
| 104 | if isa Semantic.Symbols.Function(symbol) then | |
| 105 | return _is_store_free(cast Semantic.Symbols.Function(symbol)); | |
| 106 | elif isa Semantic.Symbols.Variable(symbol) /\ symbol.type? /\ symbol.type.is_pure_function then | |
| 107 | return true; | |
| 108 | fi | |
| 109 | fi | |
| 110 | ||
| 111 | return argument.type? /\ argument.type!.is_pure_function; | |
| 112 | si | |
| 113 | ||
| 114 | // A literal is answered by what its body did, since | |
| 115 | // infer-store-free only covers named functions; a named | |
| 116 | // function by its store-free bit, which a `pure` declaration | |
| 117 | // sets without the body being provable. | |
| 118 | _is_store_free(function: Semantic.Symbols.Function) -> bool => | |
| 119 | if isa Semantic.Symbols.Closure(function) then | |
| 120 | function.is_store_free \/ !function.literal_body_impure | |
| 121 | else | |
| 122 | function.is_store_free | |
| 123 | fi; | |
| 124 | si | |
| 125 | si |