Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use Logging | |
| 3 | use Trees | |
| 4 | use Source | |
| 5 | ||
| 6 | // Enforces the pure-override contract after the effects solve | |
| 7 | // has settled the store-free answers: a function that overrides or | |
| 8 | // implements a declared-pure base must itself be pure — declared | |
| 9 | // or proven. The declared bit on the base is what call sites | |
| 10 | // trust across dispatch, including dispatch to overrides this | |
| 11 | // compilation is introducing, so an unprovable, undeclared | |
| 12 | // override is a hole in that trust and is rejected. Bases can be | |
| 13 | // local or imported: a PURE_ATTRIBUTE read on import marks the | |
| 14 | // reflected base declared-pure, so overriding a pure member from | |
| 15 | // another assembly is checked here in the overriding assembly. | |
| 16 | // | |
| 17 | // The same visit enforces the store-free mandate on the equality | |
| 18 | // and order operators: a member `=~` or `<>` must be store-free, | |
| 19 | // declared or proven. These operators are trusted wherever they | |
| 20 | // run — on operands whose type is not known when the store-free | |
| 21 | // walk runs, and through the runtime's comparer when a bare type | |
| 22 | // parameter is compared — so an implementation that could store | |
| 23 | // would make that trust unsound rather than merely unprovable. | |
| 24 | // Declaring `pure` satisfies the mandate and, through the | |
| 25 | // override contract above, binds every override of the operator | |
| 26 | // to the same rule. | |
| 27 | // | |
| 28 | // The same visit applies the purity expectation to methods and | |
| 29 | // accessors supplied through a partial or impl block whose target | |
| 30 | // is a union or a single variant: those blocks are the only way | |
| 31 | // member code reaches a union, and union state is read-only | |
| 32 | // variant fields, so a storing member there is reported as a | |
| 33 | // warning rather than rejected - declared `pure` satisfies it as | |
| 34 | // everywhere else. | |
| 35 | // | |
| 36 | // The same visit applies the type-level purity contract: instance | |
| 37 | // members of a class, struct or trait declared `pure` must be | |
| 38 | // proven store-free or declared pure. Bodiless members received the | |
| 39 | // declaration when they were declared (DECLARE_MEMBERS grants it so | |
| 40 | // implementors inherit the obligation); constructors, statics, | |
| 41 | // assign accessors, and members of unions and variants are exempt. | |
| 42 | // A public assign accessor is rejected outright on a pure type: | |
| 43 | // writes through one happen at arbitrary times from outside, which | |
| 44 | // the promise does not cover. | |
| 45 | // | |
| 46 | // The property visit enforces the stable-override contract: a | |
| 47 | // property overriding a declared-stable base must itself be | |
| 48 | // stable — declared, or self-stable by the classifier for both | |
| 49 | // fact kinds — since a call through the base can dispatch to it, | |
| 50 | // and call sites trust the base's contract across dispatch. | |
| 51 | class CHECK_PURE_OVERRIDES: ScopedVisitor is | |
| 52 | _logger: Logger | |
| 53 | ||
| 54 | init( | |
| 55 | logger: Logger, | |
| 56 | symbol_table: Semantic.SYMBOL_TABLE, | |
| 57 | namespaces: Semantic.NAMESPACES | |
| 58 | ) | |
| 59 | is | |
| 60 | super.init(logger, symbol_table, namespaces) | |
| 61 | ||
| 62 | _logger = logger | |
| 63 | si | |
| 64 | ||
| 65 | apply(root: Trees.Node) is | |
| 66 | root.walk(self) | |
| 67 | si | |
| 68 | ||
| 69 | visit(function: Definitions.FUNCTION) is | |
| 70 | super.visit(function) | |
| 71 | ||
| 72 | let symbol = symbol_for(function) | |
| 73 | ||
| 74 | if !symbol? \/ !isa Semantic.Symbols.Function(symbol) then | |
| 75 | return | |
| 76 | fi | |
| 77 | ||
| 78 | let function_symbol = cast Semantic.Symbols.Function(symbol) | |
| 79 | ||
| 80 | if _is_standard_operator(function_symbol) /\ !EFFECTS.is_store_free(function_symbol) then | |
| 81 | _logger.error( | |
| 82 | function.name!.location, | |
| 83 | "operator {function_symbol.name} must be declared pure or provably store-free" | |
| 84 | ) | |
| 85 | ||
| 86 | return | |
| 87 | fi | |
| 88 | ||
| 89 | if _union_injection_target()? /\ !EFFECTS.is_store_free(function_symbol) then | |
| 90 | // Accessors are reported through their property, the | |
| 91 | // way other diagnostics word them - never as the | |
| 92 | // synthesised functions they compile into. | |
| 93 | let message = | |
| 94 | if function.for_property? then | |
| 95 | let property_name = function.for_property.name!.name | |
| 96 | ||
| 97 | if function.is_assign_accessor then | |
| 98 | "the assign accessor of union property {property_name} must be pure" | |
| 99 | else | |
| 100 | "the getter of union property {property_name} must be pure" | |
| 101 | fi | |
| 102 | else | |
| 103 | "union method {function_symbol.name} must be pure" | |
| 104 | fi | |
| 105 | ||
| 106 | _logger.warn( | |
| 107 | function.name!.location, | |
| 108 | "impure-union-method", | |
| 109 | message | |
| 110 | ) | |
| 111 | fi | |
| 112 | ||
| 113 | let owner = _owner_classy(function_symbol) | |
| 114 | ||
| 115 | // Indexer accessors have no property to route through, and | |
| 116 | // their synthesised names never appear in source, so they | |
| 117 | // are worded and anchored through the indexer declaration. | |
| 118 | let anchor = | |
| 119 | if function.for_indexer? then | |
| 120 | function.for_indexer.location | |
| 121 | else | |
| 122 | function.name!.location | |
| 123 | fi | |
| 124 | ||
| 125 | if | |
| 126 | owner? /\ | |
| 127 | owner.is_pure /\ | |
| 128 | !_is_exempt_from_type_purity(function, function_symbol) | |
| 129 | then | |
| 130 | if !EFFECTS.is_store_free(function_symbol) then | |
| 131 | let kind = _kind_word(owner) | |
| 132 | let direction = if function.is_assign_accessor then "assign" else "read" fi | |
| 133 | ||
| 134 | // Accessors are reported through their property or | |
| 135 | // indexer, the way other diagnostics word them - | |
| 136 | // never as the synthesised functions they compile | |
| 137 | // into. | |
| 138 | let message = | |
| 139 | if function.for_property? /\ !function.is_assign_accessor then | |
| 140 | "the getter of property {function.for_property.name!.name} in pure {kind} {owner.name} must be declared pure or provably store-free" | |
| 141 | elif function.for_indexer? then | |
| 142 | "the {direction} accessor of the indexer in pure {kind} {owner.name} must be declared pure or provably store-free" | |
| 143 | else | |
| 144 | "{function_symbol.name} is a member of pure {kind} {owner.name} so must be declared pure or provably store-free" | |
| 145 | fi | |
| 146 | ||
| 147 | _logger.error( | |
| 148 | anchor, | |
| 149 | message, | |
| 150 | owner.location, | |
| 151 | "pure {kind} declared here" | |
| 152 | ) | |
| 153 | ||
| 154 | return | |
| 155 | fi | |
| 156 | fi | |
| 157 | ||
| 158 | // Accessor overrides are judged through their property, so | |
| 159 | // the message names the property rather than the | |
| 160 | // synthesised getter the accessor compiles into. | |
| 161 | if function.for_property? then | |
| 162 | return | |
| 163 | fi | |
| 164 | ||
| 165 | let overridees = function_symbol.overridees | |
| 166 | ||
| 167 | if !overridees? \/ EFFECTS.is_store_free(function_symbol) then | |
| 168 | return | |
| 169 | fi | |
| 170 | ||
| 171 | for base in overridees do | |
| 172 | if isa Semantic.Symbols.Function(base) /\ (cast Semantic.Symbols.Function(base)).is_declared_pure then | |
| 173 | let base_function = cast Semantic.Symbols.Function(base) | |
| 174 | ||
| 175 | let base_word = | |
| 176 | if base_function.is_indexer_accessor then | |
| 177 | let base_owner = _owner_classy(base_function) | |
| 178 | ||
| 179 | if base_owner? then | |
| 180 | "the indexer of {base_owner.qualified_name}" | |
| 181 | else | |
| 182 | base_function.qualified_name | |
| 183 | fi | |
| 184 | else | |
| 185 | base_function.qualified_name | |
| 186 | fi | |
| 187 | ||
| 188 | _logger.error( | |
| 189 | anchor, | |
| 190 | "{_override_subject(function, function_symbol)} overrides pure {base_word} so must be declared pure or provably store-free", | |
| 191 | base.location, | |
| 192 | "pure member declared here" | |
| 193 | ) | |
| 194 | ||
| 195 | return | |
| 196 | fi | |
| 197 | od | |
| 198 | si | |
| 199 | ||
| 200 | visit(property: Definitions.PROPERTY) is | |
| 201 | super.visit(property) | |
| 202 | ||
| 203 | let symbol = symbol_for(property) | |
| 204 | ||
| 205 | if !symbol? \/ !isa Semantic.Symbols.Property(symbol) then | |
| 206 | return | |
| 207 | fi | |
| 208 | ||
| 209 | let property_symbol = cast Semantic.Symbols.Property(symbol) | |
| 210 | ||
| 211 | let owner = _owner_classy(property_symbol) | |
| 212 | ||
| 213 | if | |
| 214 | owner? /\ | |
| 215 | owner.is_pure /\ | |
| 216 | property.modifiers.is_public | |
| 217 | then | |
| 218 | let kind = _kind_word(owner) | |
| 219 | ||
| 220 | _logger.error( | |
| 221 | property.name!.location, | |
| 222 | "{property_symbol.name} is a member of pure {kind} {owner.name} so its assign accessor cannot be public", | |
| 223 | owner.location, | |
| 224 | "pure {kind} declared here" | |
| 225 | ) | |
| 226 | fi | |
| 227 | ||
| 228 | // The pure-override contract for accessors, judged here | |
| 229 | // rather than on the accessor functions so the message | |
| 230 | // names the property: an accessor that overrides a | |
| 231 | // declared-pure one must itself be proven store-free or | |
| 232 | // declared pure. Both directions are checked - a declared- | |
| 233 | // pure assign accessor binds the overriding setter exactly | |
| 234 | // as a declared-pure getter binds the read. | |
| 235 | let pure_overridees = property_symbol.overridees | |
| 236 | ||
| 237 | if !pure_overridees? then | |
| 238 | return | |
| 239 | fi | |
| 240 | ||
| 241 | let getter = property_symbol.read_function | |
| 242 | ||
| 243 | if !(getter? /\ EFFECTS.is_store_free(getter)) then | |
| 244 | for base in pure_overridees do | |
| 245 | if isa Semantic.Symbols.Property(base) then | |
| 246 | let base_property = cast Semantic.Symbols.Property(base) | |
| 247 | let base_getter = base_property.read_function | |
| 248 | ||
| 249 | if base_getter? /\ base_getter.is_declared_pure then | |
| 250 | _logger.error( | |
| 251 | property.name!.location, | |
| 252 | "{property_symbol.name} overrides pure {base_property.qualified_name} so must be declared pure or provably store-free", | |
| 253 | base.location, | |
| 254 | "pure member declared here" | |
| 255 | ) | |
| 256 | ||
| 257 | return | |
| 258 | fi | |
| 259 | fi | |
| 260 | od | |
| 261 | fi | |
| 262 | ||
| 263 | let setter = property_symbol.assign_function | |
| 264 | ||
| 265 | if setter? /\ !EFFECTS.is_store_free(setter) then | |
| 266 | for base in pure_overridees do | |
| 267 | if isa Semantic.Symbols.Property(base) then | |
| 268 | let base_property = cast Semantic.Symbols.Property(base) | |
| 269 | let base_setter = base_property.assign_function | |
| 270 | ||
| 271 | if base_setter? /\ base_setter.is_declared_pure then | |
| 272 | _logger.error( | |
| 273 | property.name!.location, | |
| 274 | "the assign accessor of {property_symbol.name} overrides pure {base_property.qualified_name} so must be declared pure or provably store-free", | |
| 275 | base.location, | |
| 276 | "pure member declared here" | |
| 277 | ) | |
| 278 | ||
| 279 | return | |
| 280 | fi | |
| 281 | fi | |
| 282 | od | |
| 283 | fi | |
| 284 | ||
| 285 | if property_symbol.is_declared_stable then | |
| 286 | return | |
| 287 | fi | |
| 288 | ||
| 289 | let overridees = property_symbol.overridees | |
| 290 | ||
| 291 | if !overridees? then | |
| 292 | return | |
| 293 | fi | |
| 294 | ||
| 295 | for base in overridees do | |
| 296 | if isa Semantic.Symbols.Property(base) /\ (cast Semantic.Symbols.Property(base)).is_declared_stable then | |
| 297 | // Self-stable by the classifier satisfies the | |
| 298 | // contract without the declaration: the getter's | |
| 299 | // own call backs both fact kinds. | |
| 300 | if | |
| 301 | !RELIANCES.first_unproven_getter(property_symbol, null, true)? /\ | |
| 302 | !RELIANCES.first_unproven_getter(property_symbol, null, false)? | |
| 303 | then | |
| 304 | return | |
| 305 | fi | |
| 306 | ||
| 307 | _logger.error( | |
| 308 | property.name!.location, | |
| 309 | "{property_symbol.name} overrides stable {base.qualified_name} so must be declared stable or provably self-stable", | |
| 310 | base.location, | |
| 311 | "stable property declared here" | |
| 312 | ) | |
| 313 | ||
| 314 | return | |
| 315 | fi | |
| 316 | od | |
| 317 | si | |
| 318 | ||
| 319 | _is_standard_operator(function_symbol: Semantic.Symbols.Function) -> bool => | |
| 320 | (function_symbol.name =~ "=~" \/ function_symbol.name =~ "<>") /\ | |
| 321 | isa Semantic.Symbols.Classy(function_symbol.owner) | |
| 322 | ||
| 323 | _owner_classy(symbol: Semantic.Symbols.Symbol) -> Semantic.Symbols.Classy? => | |
| 324 | if isa Semantic.Symbols.Classy(symbol.owner) then | |
| 325 | cast Semantic.Symbols.Classy?(symbol.owner) | |
| 326 | else | |
| 327 | null | |
| 328 | fi | |
| 329 | ||
| 330 | _kind_word(owner: Semantic.Symbols.Classy) -> string => | |
| 331 | if owner.is_trait then | |
| 332 | "trait" | |
| 333 | elif isa Semantic.Symbols.STRUCT(owner) then | |
| 334 | "struct" | |
| 335 | else | |
| 336 | "class" | |
| 337 | fi | |
| 338 | ||
| 339 | // How to name the overriding member in a diagnostic: methods by | |
| 340 | // their symbol name, indexer accessors through the indexer - | |
| 341 | // their synthesised names never appear in source. | |
| 342 | _override_subject(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function) -> string is | |
| 343 | if !function.for_indexer? then | |
| 344 | return symbol.name | |
| 345 | fi | |
| 346 | ||
| 347 | if function.is_assign_accessor then | |
| 348 | return "the assign accessor of the indexer" | |
| 349 | fi | |
| 350 | ||
| 351 | return "the read accessor of the indexer" | |
| 352 | si | |
| 353 | ||
| 354 | _is_exempt_from_type_purity(function: Definitions.FUNCTION, symbol: Semantic.Symbols.Function) -> bool => | |
| 355 | function.modifiers.is_static \/ | |
| 356 | symbol.is_constructor \/ | |
| 357 | function.is_synthesized \/ | |
| 358 | function.is_assign_accessor \/ | |
| 359 | !function.body? \/ | |
| 360 | function.body.is_null | |
| 361 | ||
| 362 | // The target when the function just visited was declared through | |
| 363 | // a partial or impl block reopening a union or a single variant, | |
| 364 | // null for any other declaration site. Members reach a union only | |
| 365 | // through such a block - a union body holds variants alone - so | |
| 366 | // this one question covers the whole population the union purity | |
| 367 | // expectation applies to. The scoped walk leaves each visited | |
| 368 | // function's scope before this runs, so the current scope is the | |
| 369 | // block's injection scope exactly when the declaration came from | |
| 370 | // one. | |
| 371 | _union_injection_target() -> Semantic.Symbols.Classy? is | |
| 372 | if !isa Semantic.INJECTION_SCOPE(current_scope) then | |
| 373 | return null | |
| 374 | fi | |
| 375 | ||
| 376 | let target = (cast Semantic.INJECTION_SCOPE(current_scope)).target | |
| 377 | ||
| 378 | if target.is_union \/ target.is_variant then | |
| 379 | return target | |
| 380 | fi | |
| 381 | ||
| 382 | return null | |
| 383 | si | |
| 384 | si | |
| 385 | si |