1 1.1 mrg /** 2 1.1 mrg * Lazily evaluate static conditions for `static if`, `static assert` and template constraints. 3 1.1 mrg * 4 1.1 mrg * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved 5 1.1 mrg * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) 6 1.1 mrg * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 1.1 mrg * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/staticcond.d, _staticcond.d) 8 1.1 mrg * Documentation: https://dlang.org/phobos/dmd_staticcond.html 9 1.1 mrg * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/staticcond.d 10 1.1 mrg */ 11 1.1 mrg 12 1.1 mrg module dmd.staticcond; 13 1.1 mrg 14 1.1 mrg import dmd.arraytypes; 15 1.1 mrg import dmd.dmodule; 16 1.1 mrg import dmd.dscope; 17 1.1 mrg import dmd.dsymbol; 18 1.1 mrg import dmd.errors; 19 1.1 mrg import dmd.expression; 20 1.1 mrg import dmd.expressionsem; 21 1.1 mrg import dmd.globals; 22 1.1 mrg import dmd.identifier; 23 1.1 mrg import dmd.mtype; 24 1.1 mrg import dmd.root.array; 25 1.1 mrg import dmd.common.outbuffer; 26 1.1 mrg import dmd.tokens; 27 1.1 mrg 28 1.1 mrg 29 1.1 mrg 30 1.1 mrg /******************************************** 31 1.1 mrg * Semantically analyze and then evaluate a static condition at compile time. 32 1.1 mrg * This is special because short circuit operators &&, || and ?: at the top 33 1.1 mrg * level are not semantically analyzed if the result of the expression is not 34 1.1 mrg * necessary. 35 1.1 mrg * Params: 36 1.1 mrg * sc = instantiating scope 37 1.1 mrg * original = original expression, for error messages 38 1.1 mrg * e = resulting expression 39 1.1 mrg * errors = set to `true` if errors occurred 40 1.1 mrg * negatives = array to store negative clauses 41 1.1 mrg * Returns: 42 1.1 mrg * true if evaluates to true 43 1.1 mrg */ 44 1.1 mrg bool evalStaticCondition(Scope* sc, Expression original, Expression e, out bool errors, Expressions* negatives = null) 45 1.1 mrg { 46 1.1 mrg if (negatives) 47 1.1 mrg negatives.setDim(0); 48 1.1 mrg 49 1.1 mrg bool impl(Expression e) 50 1.1 mrg { 51 1.1 mrg if (e.isNotExp()) 52 1.1 mrg { 53 1.1 mrg NotExp ne = cast(NotExp)e; 54 1.1 mrg return !impl(ne.e1); 55 1.1 mrg } 56 1.1 mrg 57 1.1 mrg if (e.op == EXP.andAnd || e.op == EXP.orOr) 58 1.1 mrg { 59 1.1 mrg LogicalExp aae = cast(LogicalExp)e; 60 1.1 mrg bool result = impl(aae.e1); 61 1.1 mrg if (errors) 62 1.1 mrg return false; 63 1.1 mrg if (e.op == EXP.andAnd) 64 1.1 mrg { 65 1.1 mrg if (!result) 66 1.1 mrg return false; 67 1.1 mrg } 68 1.1 mrg else 69 1.1 mrg { 70 1.1 mrg if (result) 71 1.1 mrg return true; 72 1.1 mrg } 73 1.1 mrg result = impl(aae.e2); 74 1.1 mrg return !errors && result; 75 1.1 mrg } 76 1.1 mrg 77 1.1 mrg if (e.op == EXP.question) 78 1.1 mrg { 79 1.1 mrg CondExp ce = cast(CondExp)e; 80 1.1 mrg bool result = impl(ce.econd); 81 1.1 mrg if (errors) 82 1.1 mrg return false; 83 1.1 mrg Expression leg = result ? ce.e1 : ce.e2; 84 1.1 mrg result = impl(leg); 85 1.1 mrg return !errors && result; 86 1.1 mrg } 87 1.1 mrg 88 1.1 mrg Expression before = e; 89 1.1 mrg const uint nerrors = global.errors; 90 1.1 mrg 91 1.1 mrg sc = sc.startCTFE(); 92 1.1 mrg sc.flags |= SCOPE.condition; 93 1.1 mrg 94 1.1 mrg e = e.expressionSemantic(sc); 95 1.1 mrg e = resolveProperties(sc, e); 96 1.1 mrg e = e.toBoolean(sc); 97 1.1 mrg 98 1.1 mrg sc = sc.endCTFE(); 99 1.1 mrg e = e.optimize(WANTvalue); 100 1.1 mrg 101 1.1 mrg if (nerrors != global.errors || 102 1.1 mrg e.isErrorExp() || 103 1.1 mrg e.type.toBasetype() == Type.terror) 104 1.1 mrg { 105 1.1 mrg errors = true; 106 1.1 mrg return false; 107 1.1 mrg } 108 1.1 mrg 109 1.1 mrg e = e.ctfeInterpret(); 110 1.1 mrg 111 1.1 mrg const opt = e.toBool(); 112 1.1 mrg if (opt.isEmpty()) 113 1.1 mrg { 114 1.1 mrg e.error("expression `%s` is not constant", e.toChars()); 115 1.1 mrg errors = true; 116 1.1 mrg return false; 117 1.1 mrg } 118 1.1 mrg 119 1.1 mrg if (negatives && !opt.get()) 120 1.1 mrg negatives.push(before); 121 1.1 mrg return opt.get(); 122 1.1 mrg } 123 1.1 mrg return impl(e); 124 1.1 mrg } 125 1.1 mrg 126 1.1 mrg /******************************************** 127 1.1 mrg * Format a static condition as a tree-like structure, marking failed and 128 1.1 mrg * bypassed expressions. 129 1.1 mrg * Params: 130 1.1 mrg * original = original expression 131 1.1 mrg * instantiated = instantiated expression 132 1.1 mrg * negatives = array with negative clauses from `instantiated` expression 133 1.1 mrg * full = controls whether it shows the full output or only failed parts 134 1.1 mrg * itemCount = returns the number of written clauses 135 1.1 mrg * Returns: 136 1.1 mrg * formatted string or `null` if the expressions were `null`, or if the 137 1.1 mrg * instantiated expression is not based on the original one 138 1.1 mrg */ 139 1.1 mrg const(char)* visualizeStaticCondition(Expression original, Expression instantiated, 140 1.1 mrg const Expression[] negatives, bool full, ref uint itemCount) 141 1.1 mrg { 142 1.1 mrg if (!original || !instantiated || original.loc !is instantiated.loc) 143 1.1 mrg return null; 144 1.1 mrg 145 1.1 mrg OutBuffer buf; 146 1.1 mrg 147 1.1 mrg if (full) 148 1.1 mrg itemCount = visualizeFull(original, instantiated, negatives, buf); 149 1.1 mrg else 150 1.1 mrg itemCount = visualizeShort(original, instantiated, negatives, buf); 151 1.1 mrg 152 1.1 mrg return buf.extractChars(); 153 1.1 mrg } 154 1.1 mrg 155 1.1 mrg private uint visualizeFull(Expression original, Expression instantiated, 156 1.1 mrg const Expression[] negatives, ref OutBuffer buf) 157 1.1 mrg { 158 1.1 mrg // tree-like structure; traverse and format simultaneously 159 1.1 mrg uint count; 160 1.1 mrg uint indent; 161 1.1 mrg 162 1.1 mrg static void printOr(uint indent, ref OutBuffer buf) 163 1.1 mrg { 164 1.1 mrg buf.reserve(indent * 4 + 8); 165 1.1 mrg foreach (i; 0 .. indent) 166 1.1 mrg buf.writestring(" "); 167 1.1 mrg buf.writestring(" or:\n"); 168 1.1 mrg } 169 1.1 mrg 170 1.1 mrg // returns true if satisfied 171 1.1 mrg bool impl(Expression orig, Expression e, bool inverted, bool orOperand, bool unreached) 172 1.1 mrg { 173 1.1 mrg EXP op = orig.op; 174 1.1 mrg 175 1.1 mrg // lower all 'not' to the bottom 176 1.1 mrg // !(A && B) -> !A || !B 177 1.1 mrg // !(A || B) -> !A && !B 178 1.1 mrg if (inverted) 179 1.1 mrg { 180 1.1 mrg if (op == EXP.andAnd) 181 1.1 mrg op = EXP.orOr; 182 1.1 mrg else if (op == EXP.orOr) 183 1.1 mrg op = EXP.andAnd; 184 1.1 mrg } 185 1.1 mrg 186 1.1 mrg if (op == EXP.not) 187 1.1 mrg { 188 1.1 mrg NotExp no = cast(NotExp)orig; 189 1.1 mrg NotExp ne = cast(NotExp)e; 190 1.1 mrg assert(ne); 191 1.1 mrg return impl(no.e1, ne.e1, !inverted, orOperand, unreached); 192 1.1 mrg } 193 1.1 mrg else if (op == EXP.andAnd) 194 1.1 mrg { 195 1.1 mrg BinExp bo = cast(BinExp)orig; 196 1.1 mrg BinExp be = cast(BinExp)e; 197 1.1 mrg assert(be); 198 1.1 mrg const r1 = impl(bo.e1, be.e1, inverted, false, unreached); 199 1.1 mrg const r2 = impl(bo.e2, be.e2, inverted, false, unreached || !r1); 200 1.1 mrg return r1 && r2; 201 1.1 mrg } 202 1.1 mrg else if (op == EXP.orOr) 203 1.1 mrg { 204 1.1 mrg if (!orOperand) // do not indent A || B || C twice 205 1.1 mrg indent++; 206 1.1 mrg BinExp bo = cast(BinExp)orig; 207 1.1 mrg BinExp be = cast(BinExp)e; 208 1.1 mrg assert(be); 209 1.1 mrg const r1 = impl(bo.e1, be.e1, inverted, true, unreached); 210 1.1 mrg printOr(indent, buf); 211 1.1 mrg const r2 = impl(bo.e2, be.e2, inverted, true, unreached); 212 1.1 mrg if (!orOperand) 213 1.1 mrg indent--; 214 1.1 mrg return r1 || r2; 215 1.1 mrg } 216 1.1 mrg else if (op == EXP.question) 217 1.1 mrg { 218 1.1 mrg CondExp co = cast(CondExp)orig; 219 1.1 mrg CondExp ce = cast(CondExp)e; 220 1.1 mrg assert(ce); 221 1.1 mrg if (!inverted) 222 1.1 mrg { 223 1.1 mrg // rewrite (A ? B : C) as (A && B || !A && C) 224 1.1 mrg if (!orOperand) 225 1.1 mrg indent++; 226 1.1 mrg const r1 = impl(co.econd, ce.econd, inverted, false, unreached); 227 1.1 mrg const r2 = impl(co.e1, ce.e1, inverted, false, unreached || !r1); 228 1.1 mrg printOr(indent, buf); 229 1.1 mrg const r3 = impl(co.econd, ce.econd, !inverted, false, unreached); 230 1.1 mrg const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r3); 231 1.1 mrg if (!orOperand) 232 1.1 mrg indent--; 233 1.1 mrg return r1 && r2 || r3 && r4; 234 1.1 mrg } 235 1.1 mrg else 236 1.1 mrg { 237 1.1 mrg // rewrite !(A ? B : C) as (!A || !B) && (A || !C) 238 1.1 mrg if (!orOperand) 239 1.1 mrg indent++; 240 1.1 mrg const r1 = impl(co.econd, ce.econd, inverted, false, unreached); 241 1.1 mrg printOr(indent, buf); 242 1.1 mrg const r2 = impl(co.e1, ce.e1, inverted, false, unreached); 243 1.1 mrg const r12 = r1 || r2; 244 1.1 mrg const r3 = impl(co.econd, ce.econd, !inverted, false, unreached || !r12); 245 1.1 mrg printOr(indent, buf); 246 1.1 mrg const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r12); 247 1.1 mrg if (!orOperand) 248 1.1 mrg indent--; 249 1.1 mrg return (r1 || r2) && (r3 || r4); 250 1.1 mrg } 251 1.1 mrg } 252 1.1 mrg else // 'primitive' expression 253 1.1 mrg { 254 1.1 mrg buf.reserve(indent * 4 + 4); 255 1.1 mrg foreach (i; 0 .. indent) 256 1.1 mrg buf.writestring(" "); 257 1.1 mrg 258 1.1 mrg // find its value; it may be not computed, if there was a short circuit, 259 1.1 mrg // but we handle this case with `unreached` flag 260 1.1 mrg bool value = true; 261 1.1 mrg if (!unreached) 262 1.1 mrg { 263 1.1 mrg foreach (fe; negatives) 264 1.1 mrg { 265 1.1 mrg if (fe is e) 266 1.1 mrg { 267 1.1 mrg value = false; 268 1.1 mrg break; 269 1.1 mrg } 270 1.1 mrg } 271 1.1 mrg } 272 1.1 mrg // write the marks first 273 1.1 mrg const satisfied = inverted ? !value : value; 274 1.1 mrg if (!satisfied && !unreached) 275 1.1 mrg buf.writestring(" > "); 276 1.1 mrg else if (unreached) 277 1.1 mrg buf.writestring(" - "); 278 1.1 mrg else 279 1.1 mrg buf.writestring(" "); 280 1.1 mrg // then the expression itself 281 1.1 mrg if (inverted) 282 1.1 mrg buf.writeByte('!'); 283 1.1 mrg buf.writestring(orig.toChars); 284 1.1 mrg buf.writenl(); 285 1.1 mrg count++; 286 1.1 mrg return satisfied; 287 1.1 mrg } 288 1.1 mrg } 289 1.1 mrg 290 1.1 mrg impl(original, instantiated, false, true, false); 291 1.1 mrg return count; 292 1.1 mrg } 293 1.1 mrg 294 1.1 mrg private uint visualizeShort(Expression original, Expression instantiated, 295 1.1 mrg const Expression[] negatives, ref OutBuffer buf) 296 1.1 mrg { 297 1.1 mrg // simple list; somewhat similar to long version, so no comments 298 1.1 mrg // one difference is that it needs to hold items to display in a stack 299 1.1 mrg 300 1.1 mrg static struct Item 301 1.1 mrg { 302 1.1 mrg Expression orig; 303 1.1 mrg bool inverted; 304 1.1 mrg } 305 1.1 mrg 306 1.1 mrg Array!Item stack; 307 1.1 mrg 308 1.1 mrg bool impl(Expression orig, Expression e, bool inverted) 309 1.1 mrg { 310 1.1 mrg EXP op = orig.op; 311 1.1 mrg 312 1.1 mrg if (inverted) 313 1.1 mrg { 314 1.1 mrg if (op == EXP.andAnd) 315 1.1 mrg op = EXP.orOr; 316 1.1 mrg else if (op == EXP.orOr) 317 1.1 mrg op = EXP.andAnd; 318 1.1 mrg } 319 1.1 mrg 320 1.1 mrg if (op == EXP.not) 321 1.1 mrg { 322 1.1 mrg NotExp no = cast(NotExp)orig; 323 1.1 mrg NotExp ne = cast(NotExp)e; 324 1.1 mrg assert(ne); 325 1.1 mrg return impl(no.e1, ne.e1, !inverted); 326 1.1 mrg } 327 1.1 mrg else if (op == EXP.andAnd) 328 1.1 mrg { 329 1.1 mrg BinExp bo = cast(BinExp)orig; 330 1.1 mrg BinExp be = cast(BinExp)e; 331 1.1 mrg assert(be); 332 1.1 mrg bool r = impl(bo.e1, be.e1, inverted); 333 1.1 mrg r = r && impl(bo.e2, be.e2, inverted); 334 1.1 mrg return r; 335 1.1 mrg } 336 1.1 mrg else if (op == EXP.orOr) 337 1.1 mrg { 338 1.1 mrg BinExp bo = cast(BinExp)orig; 339 1.1 mrg BinExp be = cast(BinExp)e; 340 1.1 mrg assert(be); 341 1.1 mrg const lbefore = stack.length; 342 1.1 mrg bool r = impl(bo.e1, be.e1, inverted); 343 1.1 mrg r = r || impl(bo.e2, be.e2, inverted); 344 1.1 mrg if (r) 345 1.1 mrg stack.setDim(lbefore); // purge added positive items 346 1.1 mrg return r; 347 1.1 mrg } 348 1.1 mrg else if (op == EXP.question) 349 1.1 mrg { 350 1.1 mrg CondExp co = cast(CondExp)orig; 351 1.1 mrg CondExp ce = cast(CondExp)e; 352 1.1 mrg assert(ce); 353 1.1 mrg if (!inverted) 354 1.1 mrg { 355 1.1 mrg const lbefore = stack.length; 356 1.1 mrg bool a = impl(co.econd, ce.econd, inverted); 357 1.1 mrg a = a && impl(co.e1, ce.e1, inverted); 358 1.1 mrg bool b; 359 1.1 mrg if (!a) 360 1.1 mrg { 361 1.1 mrg b = impl(co.econd, ce.econd, !inverted); 362 1.1 mrg b = b && impl(co.e2, ce.e2, inverted); 363 1.1 mrg } 364 1.1 mrg const r = a || b; 365 1.1 mrg if (r) 366 1.1 mrg stack.setDim(lbefore); 367 1.1 mrg return r; 368 1.1 mrg } 369 1.1 mrg else 370 1.1 mrg { 371 1.1 mrg bool a; 372 1.1 mrg { 373 1.1 mrg const lbefore = stack.length; 374 1.1 mrg a = impl(co.econd, ce.econd, inverted); 375 1.1 mrg a = a || impl(co.e1, ce.e1, inverted); 376 1.1 mrg if (a) 377 1.1 mrg stack.setDim(lbefore); 378 1.1 mrg } 379 1.1 mrg bool b; 380 1.1 mrg if (a) 381 1.1 mrg { 382 1.1 mrg const lbefore = stack.length; 383 1.1 mrg b = impl(co.econd, ce.econd, !inverted); 384 1.1 mrg b = b || impl(co.e2, ce.e2, inverted); 385 1.1 mrg if (b) 386 1.1 mrg stack.setDim(lbefore); 387 1.1 mrg } 388 1.1 mrg return a && b; 389 1.1 mrg } 390 1.1 mrg } 391 1.1 mrg else // 'primitive' expression 392 1.1 mrg { 393 1.1 mrg bool value = true; 394 1.1 mrg foreach (fe; negatives) 395 1.1 mrg { 396 1.1 mrg if (fe is e) 397 1.1 mrg { 398 1.1 mrg value = false; 399 1.1 mrg break; 400 1.1 mrg } 401 1.1 mrg } 402 1.1 mrg const satisfied = inverted ? !value : value; 403 1.1 mrg if (!satisfied) 404 1.1 mrg stack.push(Item(orig, inverted)); 405 1.1 mrg return satisfied; 406 1.1 mrg } 407 1.1 mrg } 408 1.1 mrg 409 1.1 mrg impl(original, instantiated, false); 410 1.1 mrg 411 1.1 mrg foreach (i; 0 .. stack.length) 412 1.1 mrg { 413 1.1 mrg // write the expression only 414 1.1 mrg buf.writestring(" "); 415 1.1 mrg if (stack[i].inverted) 416 1.1 mrg buf.writeByte('!'); 417 1.1 mrg buf.writestring(stack[i].orig.toChars); 418 1.1 mrg // here with no trailing newline 419 1.1 mrg if (i + 1 < stack.length) 420 1.1 mrg buf.writenl(); 421 1.1 mrg } 422 1.1 mrg return cast(uint)stack.length; 423 1.1 mrg } 424