Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use System.Exception | |
| 3 | ||
| 4 | use Ghul.Pipes | |
| 5 | ||
| 6 | use Logging | |
| 7 | use Trees | |
| 8 | ||
| 9 | use Semantic.Types.Type | |
| 10 | ||
| 11 | use Function = Semantic.Symbols.Function | |
| 12 | use Symbol = Semantic.Symbols.Symbol | |
| 13 | ||
| 14 | // Variable declarations and the no-op default visits for node kinds that cannot store. | |
| 15 | partial INFER_STORE_FREE is | |
| 16 | // ==== local variable declarations ==== | |
| 17 | ||
| 18 | visit(variable: Variables.VARIABLE) is | |
| 19 | if !_current? then | |
| 20 | return | |
| 21 | fi | |
| 22 | ||
| 23 | if variable.want_dispose then | |
| 24 | // `let use` runs dispose() at scope exit. Before | |
| 25 | // compile-expressions the value's type is unknown, so | |
| 26 | // the disposal cannot be bounded; after it, the | |
| 27 | // dispose member resolves like any other callee. | |
| 28 | if !_resolved_mode then | |
| 29 | _disqualify_because("let-use-dispose") | |
| 30 | return | |
| 31 | fi | |
| 32 | ||
| 33 | let disposed_type = _typer.try_type(variable.initializer) | |
| 34 | ||
| 35 | if !disposed_type? then | |
| 36 | _disqualify_because("let-use-dispose-untyped") | |
| 37 | return | |
| 38 | fi | |
| 39 | ||
| 40 | let dispose = disposed_type.find_member("dispose") | |
| 41 | ||
| 42 | if !dispose? then | |
| 43 | _disqualify_because("let-use-dispose-unresolved") | |
| 44 | return | |
| 45 | fi | |
| 46 | ||
| 47 | _add_callee_edges(dispose) | |
| 48 | fi | |
| 49 | ||
| 50 | if variable.is_argument then | |
| 51 | // parameter types come from the symbol; a default | |
| 52 | // value expression is walked as part of this body and | |
| 53 | // classified like any other code | |
| 54 | return | |
| 55 | fi | |
| 56 | ||
| 57 | let name = variable.name | |
| 58 | ||
| 59 | if !name? then | |
| 60 | return | |
| 61 | fi | |
| 62 | ||
| 63 | let symbol = find(name.name) | |
| 64 | ||
| 65 | if !symbol? \/ !isa Semantic.Symbols.LOCAL_VARIABLE(symbol) then | |
| 66 | return | |
| 67 | fi | |
| 68 | ||
| 69 | if _resolved_mode /\ !(cast Semantic.Symbols.LOCAL_VARIABLE(symbol)).is_mutable_marked then | |
| 70 | let fn = function_value_of( | |
| 71 | if variable.initializer? then variable.initializer.value else null fi) | |
| 72 | ||
| 73 | if fn? then | |
| 74 | function_values.note_local(symbol, fn) | |
| 75 | fi | |
| 76 | fi | |
| 77 | ||
| 78 | // An explicitly-declared local carries its type on the | |
| 79 | // declaration node, resolved before this pass. Read it there, | |
| 80 | // not from the symbol: the symbol's `type` field is also | |
| 81 | // written later by compile-expressions inference, so reading | |
| 82 | // it would make the store-free classification depend on | |
| 83 | // whether that later pass has run — it has in an analysis-mode | |
| 84 | // refresh, it has not in a batch build. | |
| 85 | if !isa TypeExpressions.INFER(variable.type_expression) then | |
| 86 | if let declared = _typer.usable(variable.type_expression.type) then | |
| 87 | _typer.set_local_type(symbol, declared) | |
| 88 | fi | |
| 89 | ||
| 90 | return | |
| 91 | fi | |
| 92 | ||
| 93 | let initializer_type = _typer.try_type(variable.initializer) | |
| 94 | ||
| 95 | if initializer_type? then | |
| 96 | _typer.set_local_type(symbol, initializer_type) | |
| 97 | fi | |
| 98 | si | |
| 99 | ||
| 100 | // An assert's condition and message classify like any other | |
| 101 | // code as the walk reaches them; the construct-and-throw | |
| 102 | // machinery a failing assert runs is compiler-synthesised — | |
| 103 | // it allocates a fresh exception and stores nothing that | |
| 104 | // existed before it, so even a caller that catches the | |
| 105 | // failure observes an unchanged heap. | |
| 106 | visit(`assert: Statements.ASSERT) is si | |
| 107 | visit(assert_in: Expressions.ASSERT_IN) is si | |
| 108 | ||
| 109 | // Interpolation formats each fragment by calling to_string | |
| 110 | // on it. When a fragment's static type is known, that | |
| 111 | // dispatch is bounded like any other member call — the | |
| 112 | // fixpoint checks the type's to_string and every override a | |
| 113 | // call could reach; primitive and object to_string bodies | |
| 114 | // are trusted imports. An untyped fragment, or one with a | |
| 115 | // format specifier (which selects a different, culture-aware | |
| 116 | // formatting path), cannot be bounded. | |
| 117 | visit(interpolation: Expressions.STRING_INTERPOLATION) is | |
| 118 | if !_current? then | |
| 119 | return | |
| 120 | fi | |
| 121 | ||
| 122 | for fragment in interpolation.values do | |
| 123 | if fragment.is_expression then | |
| 124 | if fragment.format? then | |
| 125 | _disqualify_because("interpolation-format") | |
| 126 | return | |
| 127 | fi | |
| 128 | ||
| 129 | let fragment_type = _typer.try_type(fragment.expression) | |
| 130 | ||
| 131 | if !fragment_type? then | |
| 132 | _disqualify_because("interpolation-fragment-untyped") | |
| 133 | return | |
| 134 | fi | |
| 135 | ||
| 136 | _add_callee_edges(fragment_type.find_member("to_string")) | |
| 137 | fi | |
| 138 | od | |
| 139 | si | |
| 140 | ||
| 141 | // ==== innate bodies ==== | |
| 142 | ||
| 143 | visit(block: Bodies.INNATE) is | |
| 144 | if !_current? then | |
| 145 | return | |
| 146 | fi | |
| 147 | ||
| 148 | if !STORE_FREE_INNATES.is_store_free(block.name) then | |
| 149 | _disqualify_because("innate-not-store-free") | |
| 150 | fi | |
| 151 | si | |
| 152 | ||
| 153 | // ==== audited-harmless node kinds ==== | |
| 154 | // | |
| 155 | // Everything below either has no effect of its own (its | |
| 156 | // children are classified independently as the walk reaches | |
| 157 | // them) or cannot occur in an executable position. | |
| 158 | ||
| 159 | visit(identifier: Identifiers.Identifier) is si | |
| 160 | visit(identifier: Identifiers.QUALIFIED) is si | |
| 161 | visit(modifier: Modifiers.Modifier) is si | |
| 162 | visit(modifiers: Modifiers.LIST) is si | |
| 163 | visit(pragma: Pragmas.PRAGMA) is si | |
| 164 | visit(pragma: Statements.PRAGMA) is si | |
| 165 | ||
| 166 | visit(type_expression: TypeExpressions.TypeExpression) is si | |
| 167 | visit(type_expression: TypeExpressions.INFER) is si | |
| 168 | visit(structured: TypeExpressions.Structured) is si | |
| 169 | visit(array: TypeExpressions.ARRAY_) is si | |
| 170 | visit(pointer: TypeExpressions.POINTER) is si | |
| 171 | visit(optional: TypeExpressions.OPTIONAL) is si | |
| 172 | visit(reference: TypeExpressions.REFERENCE) is si | |
| 173 | visit(member: TypeExpressions.MEMBER) is si | |
| 174 | visit(named: TypeExpressions.NAMED) is si | |
| 175 | visit(types: TypeExpressions.LIST) is si | |
| 176 | visit(generic: TypeExpressions.GENERIC) is si | |
| 177 | visit(function: TypeExpressions.FUNCTION) is si | |
| 178 | visit(functions: TypeExpressions.FUNCTION_GROUP) is si | |
| 179 | visit(tuple: TypeExpressions.TUPLE) is si | |
| 180 | visit(element: TypeExpressions.NAMED_TUPLE_ELEMENT) is si | |
| 181 | visit(element: TypeExpressions.UNDEFINED) is si | |
| 182 | visit(constraint: TypeExpressions.TYPE_PARAMETER_CONSTRAINT) is si | |
| 183 | ||
| 184 | visit(literal: Expressions.Literals.Literal) is si | |
| 185 | visit(`string: Expressions.Literals.STRING) is si | |
| 186 | visit(integer: Expressions.Literals.INTEGER) is si | |
| 187 | visit(float: Expressions.Literals.FLOAT) is si | |
| 188 | visit(character: Expressions.Literals.CHARACTER) is si | |
| 189 | visit(boolean: Expressions.Literals.BOOLEAN) is si | |
| 190 | visit(none: Expressions.Literals.NONE) is si | |
| 191 | ||
| 192 | visit(`null: Expressions.NULL) is si | |
| 193 | visit(`self: Expressions.SELF) is si | |
| 194 | visit(`super: Expressions.SUPER) is si | |
| 195 | visit(`cast: Expressions.CAST) is si | |
| 196 | visit(`isa: Expressions.ISA) is si | |
| 197 | visit(`isa: Expressions.TYPEOF) is si | |
| 198 | visit(`default: Expressions.DEFAULT) is si | |
| 199 | visit(has_value: Expressions.HAS_VALUE) is si | |
| 200 | visit(unwrap: Expressions.UNWRAP) is si | |
| 201 | visit(tuple: Expressions.TUPLE) is si | |
| 202 | visit(variable: Expressions.TUPLE_ELEMENT) is si | |
| 203 | visit(sequence: Expressions.SEQUENCE) is si | |
| 204 | visit(list: Expressions.LIST) is si | |
| 205 | visit(variable: Expressions.VARIABLE) is si | |
| 206 | visit(statement: Expressions.STATEMENT) is si | |
| 207 | visit(block: Expressions.VAL_BLOCK) is si | |
| 208 | ||
| 209 | // In resolved mode the disposal is bounded per variable, from | |
| 210 | // the initializer's compiled type - see the want_dispose case | |
| 211 | // in visit(variable) - so the statement-level catch-alls only | |
| 212 | // fire when the type was unknowable. | |
| 213 | visit(statement: Expressions.LET_IN) is | |
| 214 | if statement.want_dispose /\ !_resolved_mode then | |
| 215 | _disqualify_because("let-in-dispose") | |
| 216 | fi | |
| 217 | ||
| 218 | leave_scope(statement) | |
| 219 | si | |
| 220 | ||
| 221 | visit(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) is si | |
| 222 | visit(destructure_left: Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION) is si | |
| 223 | ||
| 224 | visit(left: Trees.Variables.SIMPLE_VARIABLE_LEFT) is si | |
| 225 | visit(destructure_left: Trees.Variables.DESTRUCTURING_VARIABLE_LEFT) is si | |
| 226 | visit(left: Trees.Variables.LITERAL_VARIABLE_LEFT) is si | |
| 227 | visit(variables: Variables.LIST) is si | |
| 228 | ||
| 229 | visit(statements: Statements.LIST) is si | |
| 230 | visit(expression: Statements.EXPRESSION) is si | |
| 231 | visit(`return: Statements.RETURN) is si | |
| 232 | visit(`throw: Statements.THROW) is si | |
| 233 | visit(`if: Statements.IF) is si | |
| 234 | visit(`break: Statements.BREAK) is si | |
| 235 | visit(`continue: Statements.CONTINUE) is si | |
| 236 | visit(rb: Statements.REFUTABLE_BINDING) is si | |
| 237 | ||
| 238 | visit(l: Statements.LET) is | |
| 239 | if l.want_dispose /\ !_resolved_mode then | |
| 240 | _disqualify_because("let-dispose") | |
| 241 | fi | |
| 242 | si | |
| 243 | ||
| 244 | visit(if_branch: Statements.IF_BRANCH) is | |
| 245 | leave_scope(if_branch) | |
| 246 | si | |
| 247 | ||
| 248 | visit(`case: Statements.CASE) is | |
| 249 | leave_scope(`case) | |
| 250 | si | |
| 251 | ||
| 252 | visit(case_match: Statements.CASE_MATCH) is | |
| 253 | leave_scope(case_match) | |
| 254 | si | |
| 255 | ||
| 256 | visit(`try: Statements.TRY) is | |
| 257 | leave_scope(`try) | |
| 258 | si | |
| 259 | ||
| 260 | visit(`catch: Statements.CATCH) is | |
| 261 | leave_scope(`catch) | |
| 262 | si | |
| 263 | ||
| 264 | visit(`do: Statements.DO) is | |
| 265 | leave_scope(`do) | |
| 266 | si | |
| 267 | ||
| 268 | visit(labelled: Statements.LABELLED) is | |
| 269 | si | |
| 270 | ||
| 271 | visit(expression: Bodies.EXPRESSION) is | |
| 272 | leave_scope(expression) | |
| 273 | si | |
| 274 | ||
| 275 | visit(block: Bodies.BLOCK) is | |
| 276 | leave_scope(block) | |
| 277 | si | |
| 278 | ||
| 279 | // a bodiless declaration stores nothing; whether calls to it | |
| 280 | // are safe is decided by its overriders through the fixpoint | |
| 281 | visit(block: Bodies.NULL) is si | |
| 282 | si | |
| 283 | si |