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