Appearance
| 1 | namespace Semantic.Symbols is | |
| 2 | // Imported functions trusted not to store to any pre-existing, | |
| 3 | // user-visible heap location. Curation rules, in order of trust: | |
| 4 | // | |
| 5 | // - Sealed-receiver members (string, the numeric primitives) | |
| 6 | // cannot dispatch anywhere else and take no function-typed | |
| 7 | // arguments, so nothing can re-enter user code. | |
| 8 | // - Non-virtual members of the concrete collection classes | |
| 9 | // (LIST, STACK) dispatch to exactly one BCL body. Reads that | |
| 10 | // consult an equality comparer (MAP and SET lookups, and the | |
| 11 | // read-only interface views) additionally trust the standard | |
| 12 | // .NET contract that a key type's equals / hash members do | |
| 13 | // not mutate; a pathological key type can break that, which | |
| 14 | // is accepted deliberately. | |
| 15 | // - Virtual members (object.to_string) are trusted only for | |
| 16 | // the imported body; the fixpoint separately checks their | |
| 17 | // in-assembly overriders, so a storing override still | |
| 18 | // poisons every call that could dispatch to it. | |
| 19 | // | |
| 20 | // Members that run user callbacks structurally (delegate-taking | |
| 21 | // LINQ shapes, sort with comparers) must never appear here. | |
| 22 | class STORE_FREE_IMPORTS is | |
| 23 | _entries: Collections.SET[string]? static; | |
| 24 | ||
| 25 | entries() -> Collections.SET[string] static is | |
| 26 | if !_entries? then | |
| 27 | let result = Collections.SET[string](); | |
| 28 | ||
| 29 | // string: sealed, no callbacks | |
| 30 | result.add("Ghul.string.get_length"); | |
| 31 | result.add("Ghul.string.$get_length"); | |
| 32 | result.add("Ghul.string.length"); | |
| 33 | result.add("Ghul.string.index_of"); | |
| 34 | result.add("Ghul.string.last_index_of"); | |
| 35 | result.add("Ghul.string.substring"); | |
| 36 | result.add("Ghul.string.contains"); | |
| 37 | result.add("Ghul.string.starts_with"); | |
| 38 | result.add("Ghul.string.ends_with"); | |
| 39 | result.add("Ghul.string.to_string"); | |
| 40 | ||
| 41 | // string: further sealed reads and transforms. Each | |
| 42 | // returns a fresh string, a fresh array or a value and | |
| 43 | // takes only value types, strings, char arrays or the | |
| 44 | // StringComparison / StringSplitOptions enums — no | |
| 45 | // overload of any of these names takes a delegate, an | |
| 46 | // object or an interface, so none can dispatch into | |
| 47 | // user code. | |
| 48 | result.add("Ghul.string.split"); | |
| 49 | result.add("Ghul.string.trim"); | |
| 50 | result.add("Ghul.string.trim_start"); | |
| 51 | result.add("Ghul.string.trim_end"); | |
| 52 | result.add("Ghul.string.pad_left"); | |
| 53 | result.add("Ghul.string.pad_right"); | |
| 54 | result.add("Ghul.string.remove"); | |
| 55 | result.add("Ghul.string.to_char_array"); | |
| 56 | result.add("Ghul.string.index_of_any"); | |
| 57 | result.add("Ghul.string.last_index_of_any"); | |
| 58 | result.add("Ghul.string.is_null_or_empty"); | |
| 59 | result.add("Ghul.string.is_null_or_white_space"); | |
| 60 | result.add("Ghul.string.to_lower_invariant"); | |
| 61 | result.add("Ghul.string.to_upper_invariant"); | |
| 62 | ||
| 63 | // string: case conversion and replacement. The | |
| 64 | // no-argument and ordinal forms are sealed reads; the | |
| 65 | // names also cover overloads taking a CultureInfo, | |
| 66 | // which is not sealed, so a pathological custom culture | |
| 67 | // could in principle re-enter user code during | |
| 68 | // formatting. That is the same deliberately-accepted | |
| 69 | // risk as the equals-and-hash contract above, and the | |
| 70 | // compiler only ever calls the culture-free forms. | |
| 71 | result.add("Ghul.string.to_lower"); | |
| 72 | result.add("Ghul.string.to_upper"); | |
| 73 | result.add("Ghul.string.replace"); | |
| 74 | ||
| 75 | // primitive to_string: exact (constrained) dispatch | |
| 76 | result.add("Ghul.int.to_string"); | |
| 77 | result.add("Ghul.long.to_string"); | |
| 78 | result.add("Ghul.short.to_string"); | |
| 79 | result.add("Ghul.byte.to_string"); | |
| 80 | result.add("Ghul.ubyte.to_string"); | |
| 81 | result.add("Ghul.uint.to_string"); | |
| 82 | result.add("Ghul.ulong.to_string"); | |
| 83 | result.add("Ghul.ushort.to_string"); | |
| 84 | result.add("Ghul.bool.to_string"); | |
| 85 | result.add("Ghul.char.to_string"); | |
| 86 | result.add("Ghul.single.to_string"); | |
| 87 | result.add("Ghul.double.to_string"); | |
| 88 | ||
| 89 | // char: the Unicode classifiers and case converters are | |
| 90 | // statics on a sealed struct. Every overload takes only | |
| 91 | // value types - a char, a (string, int) pair, or the | |
| 92 | // (char, char) and (char, char, char) forms of the | |
| 93 | // range and surrogate checks - and returns a bool, an | |
| 94 | // enum, a numeric value or a char, so none can dispatch | |
| 95 | // into user code. The culture-taking converters | |
| 96 | // (to_lower / to_upper with a CultureInfo) carry the | |
| 97 | // same accepted pathological-culture caveat noted for | |
| 98 | // string case conversion above; the invariant forms do | |
| 99 | // not. | |
| 100 | result.add("Ghul.char.is_white_space"); | |
| 101 | result.add("Ghul.char.is_digit"); | |
| 102 | result.add("Ghul.char.is_letter"); | |
| 103 | result.add("Ghul.char.is_letter_or_digit"); | |
| 104 | result.add("Ghul.char.is_upper"); | |
| 105 | result.add("Ghul.char.is_lower"); | |
| 106 | result.add("Ghul.char.is_number"); | |
| 107 | result.add("Ghul.char.is_symbol"); | |
| 108 | result.add("Ghul.char.is_control"); | |
| 109 | result.add("Ghul.char.is_punctuation"); | |
| 110 | result.add("Ghul.char.is_separator"); | |
| 111 | result.add("Ghul.char.is_surrogate"); | |
| 112 | result.add("Ghul.char.is_high_surrogate"); | |
| 113 | result.add("Ghul.char.is_low_surrogate"); | |
| 114 | result.add("Ghul.char.is_surrogate_pair"); | |
| 115 | result.add("Ghul.char.is_between"); | |
| 116 | result.add("Ghul.char.is_ascii"); | |
| 117 | result.add("Ghul.char.is_ascii_digit"); | |
| 118 | result.add("Ghul.char.is_ascii_letter"); | |
| 119 | result.add("Ghul.char.is_ascii_letter_or_digit"); | |
| 120 | result.add("Ghul.char.is_ascii_letter_lower"); | |
| 121 | result.add("Ghul.char.is_ascii_letter_upper"); | |
| 122 | result.add("Ghul.char.is_ascii_hex_digit"); | |
| 123 | result.add("Ghul.char.is_ascii_hex_digit_lower"); | |
| 124 | result.add("Ghul.char.is_ascii_hex_digit_upper"); | |
| 125 | result.add("Ghul.char.get_unicode_category"); | |
| 126 | result.add("Ghul.char.get_numeric_value"); | |
| 127 | result.add("Ghul.char.to_lower_invariant"); | |
| 128 | result.add("Ghul.char.to_upper_invariant"); | |
| 129 | result.add("Ghul.char.to_lower"); | |
| 130 | result.add("Ghul.char.to_upper"); | |
| 131 | ||
| 132 | // object.to_string: the imported body prints a type | |
| 133 | // name; storing overrides are caught via overrider | |
| 134 | // links | |
| 135 | result.add("Ghul.object.to_string"); | |
| 136 | ||
| 137 | // concrete collections: non-virtual reads | |
| 138 | result.add("Collections.LIST.get_Item"); | |
| 139 | result.add("Collections.LIST.get_count"); | |
| 140 | result.add("Collections.LIST.$get_count"); | |
| 141 | result.add("Collections.LIST.count"); | |
| 142 | result.add("Collections.STACK.peek"); | |
| 143 | result.add("Collections.STACK.get_count"); | |
| 144 | result.add("Collections.STACK.$get_count"); | |
| 145 | result.add("Collections.STACK.count"); | |
| 146 | ||
| 147 | // dictionary / set reads: trusts the equals-and-hash | |
| 148 | // contract of the key type | |
| 149 | result.add("Collections.MAP.get_Item"); | |
| 150 | result.add("Collections.MAP.contains_key"); | |
| 151 | result.add("Collections.MAP.try_get_value"); | |
| 152 | result.add("Collections.MAP.get_count"); | |
| 153 | result.add("Collections.MAP.$get_count"); | |
| 154 | result.add("Collections.MAP.count"); | |
| 155 | result.add("Collections.SET.contains"); | |
| 156 | result.add("Collections.SET.get_count"); | |
| 157 | result.add("Collections.SET.$get_count"); | |
| 158 | result.add("Collections.SET.count"); | |
| 159 | ||
| 160 | // read-only interface views: same reads through the | |
| 161 | // IReadOnly* interfaces; trusts implementations to | |
| 162 | // honour the read-only contract | |
| 163 | result.add("Collections.List.get_Item"); | |
| 164 | result.add("Collections.List.get_count"); | |
| 165 | result.add("Collections.List.$get_count"); | |
| 166 | result.add("Collections.List.count"); | |
| 167 | result.add("Collections.Map.get_Item"); | |
| 168 | result.add("Collections.Map.contains_key"); | |
| 169 | result.add("Collections.Map.try_get_value"); | |
| 170 | result.add("Collections.Map.get_count"); | |
| 171 | result.add("Collections.Map.$get_count"); | |
| 172 | result.add("Collections.Map.count"); | |
| 173 | result.add("Collections.Bag.get_count"); | |
| 174 | result.add("Collections.Bag.$get_count"); | |
| 175 | result.add("Collections.Bag.count"); | |
| 176 | ||
| 177 | // value-type optional carriers: `x?` / `x!` on an | |
| 178 | // `int?`-style slot lower to these accessors, which | |
| 179 | // read a field of a sealed struct and can dispatch | |
| 180 | // nowhere else. Without them every value-type | |
| 181 | // presence test would count as a possibly-storing | |
| 182 | // call and kill the very facts it establishes. | |
| 183 | result.add("System.Nullable.get_has_value"); | |
| 184 | result.add("System.Nullable.$get_has_value"); | |
| 185 | result.add("System.Nullable.has_value"); | |
| 186 | result.add("System.Nullable.get_value"); | |
| 187 | result.add("System.Nullable.$get_value"); | |
| 188 | result.add("System.Nullable.value"); | |
| 189 | result.add("Ghul.MAYBE.get_has_value"); | |
| 190 | result.add("Ghul.MAYBE.$get_has_value"); | |
| 191 | result.add("Ghul.MAYBE.has_value"); | |
| 192 | result.add("Ghul.MAYBE.get_value"); | |
| 193 | result.add("Ghul.MAYBE.$get_value"); | |
| 194 | result.add("Ghul.MAYBE.value"); | |
| 195 | ||
| 196 | // console writes: console state is not user-visible | |
| 197 | // heap, so a debug print cannot invalidate a | |
| 198 | // narrowing. The object-taking overloads format via | |
| 199 | // to_string, where a pathological storing override | |
| 200 | // could slip through — accepted so that adding debug | |
| 201 | // output does not silently break dependent | |
| 202 | // narrowings. | |
| 203 | result.add("IO.Std.write"); | |
| 204 | result.add("IO.Std.write_line"); | |
| 205 | result.add("IO.Std.get_error"); | |
| 206 | result.add("IO.Std.$get_error"); | |
| 207 | result.add("IO.Std.error"); | |
| 208 | result.add("IO.Std.get_out"); | |
| 209 | result.add("IO.Std.$get_out"); | |
| 210 | result.add("IO.Std.out"); | |
| 211 | result.add("IO.TextWriter.write"); | |
| 212 | result.add("IO.TextWriter.write_line"); | |
| 213 | ||
| 214 | // path manipulation: statics on a sealed class that | |
| 215 | // combine and dissect path strings. Every overload | |
| 216 | // takes only strings and returns a string or a bool; | |
| 217 | // none takes a delegate, an object or an interface, so | |
| 218 | // no call reaches user code. These read process state | |
| 219 | // (the current directory, for get_full_path) but write | |
| 220 | // no user-visible heap. The file-touching members | |
| 221 | // (get_temp_file_name, and the filesystem probes) are | |
| 222 | // deliberately left out. | |
| 223 | result.add("IO.Path.combine"); | |
| 224 | result.add("IO.Path.join"); | |
| 225 | result.add("IO.Path.get_directory_name"); | |
| 226 | result.add("IO.Path.get_file_name"); | |
| 227 | result.add("IO.Path.get_file_name_without_extension"); | |
| 228 | result.add("IO.Path.get_extension"); | |
| 229 | result.add("IO.Path.get_full_path"); | |
| 230 | result.add("IO.Path.get_path_root"); | |
| 231 | result.add("IO.Path.get_relative_path"); | |
| 232 | result.add("IO.Path.change_extension"); | |
| 233 | result.add("IO.Path.has_extension"); | |
| 234 | result.add("IO.Path.is_path_rooted"); | |
| 235 | result.add("IO.Path.is_path_fully_qualified"); | |
| 236 | result.add("IO.Path.ends_in_directory_separator"); | |
| 237 | result.add("IO.Path.trim_ending_directory_separator"); | |
| 238 | ||
| 239 | // object.get_type: unlike to_string, GetType is not | |
| 240 | // virtual, so no override can exist and no poisoning is | |
| 241 | // needed — it reads the runtime type and returns it. | |
| 242 | result.add("Ghul.object.get_type"); | |
| 243 | ||
| 244 | // StringBuilder reads: the sealed builder's to_string | |
| 245 | // and length getter only read its contents. The | |
| 246 | // mutating members (append, insert, clear, the length | |
| 247 | // and Chars setters) are excluded. | |
| 248 | result.add("System.Text.StringBuilder.to_string"); | |
| 249 | result.add("System.Text.StringBuilder.get_length"); | |
| 250 | result.add("System.Text.StringBuilder.$get_length"); | |
| 251 | result.add("System.Text.StringBuilder.length"); | |
| 252 | ||
| 253 | // reflected type name reads: name and full_name on a | |
| 254 | // System.Type. Type is abstract, so a custom subclass | |
| 255 | // could override these getters to store — the same | |
| 256 | // accepted pathological-implementation risk as the | |
| 257 | // equals-and-hash contract above; the compiler only | |
| 258 | // ever reads them off runtime types. | |
| 259 | result.add("System.Type.name"); | |
| 260 | result.add("System.Type.get_name"); | |
| 261 | result.add("System.Type.$get_name"); | |
| 262 | result.add("System.Type.full_name"); | |
| 263 | result.add("System.Type.get_full_name"); | |
| 264 | result.add("System.Type.$get_full_name"); | |
| 265 | ||
| 266 | // KeyValuePair construction: the read-only pair struct's | |
| 267 | // constructor copies its key and value into the fresh | |
| 268 | // struct and runs no behaviour on them, so it writes no | |
| 269 | // pre-existing heap slot. Trusting it lets a dictionary | |
| 270 | // populated from a pipe of pairs keep its narrowings — a | |
| 271 | // storing argument still disqualifies where it is | |
| 272 | // classified. | |
| 273 | result.add("Collections.KeyValuePair.init"); | |
| 274 | ||
| 275 | _entries = result; | |
| 276 | fi | |
| 277 | ||
| 278 | return _entries; | |
| 279 | si | |
| 280 | ||
| 281 | is_store_free(function: Function?) -> bool static is | |
| 282 | if !function? \/ !function.is_reflected then | |
| 283 | return false; | |
| 284 | fi | |
| 285 | ||
| 286 | let owner = function.owner; | |
| 287 | ||
| 288 | if !owner? then | |
| 289 | return false; | |
| 290 | fi | |
| 291 | ||
| 292 | let key = "{owner.qualified_name}.{function.name}"; | |
| 293 | return entries().contains(key); | |
| 294 | si | |
| 295 | si | |
| 296 | si |