From 10ed8bfc2f1d9d571fcb32529e388c9a529f0147 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Thu, 19 Oct 2023 18:01:34 +0200 Subject: [PATCH] feat: basic implementation of partial evaluator service (#649) Closes partially #603 ### Summary of Changes * Add partial evaluator service. This replaces the old `toConstantExpressionOrUndefined` function. * Improve the model classes that represent evaluation results. * Evaluate more types of expressions. * Add more tests. Still TODO in a future PR: Add tests for the model classes, particularly for their `toString` and `equals` methods, and include them in the coverage computation. --------- Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> --- .../development/partial-evaluation-testing.md | 2 +- img/safe-ds_file_icon_dark.svg | 32 ++ img/safe-ds_file_icon_light.svg | 8 + img/safe-ds_logo_rounded.png | Bin 9930 -> 0 bytes img/safe-ds_logo_rounded_128x128.png | Bin 0 -> 2947 bytes package.json | 8 +- src/language/builtins/safe-ds-annotations.ts | 16 +- src/language/partialEvaluation/model.ts | 383 ++++++++----- .../safe-ds-partial-evaluator.ts | 523 ++++++++++++++++++ .../partialEvaluation/toConstantExpression.ts | 469 ---------------- src/language/safe-ds-module.ts | 7 + src/language/typing/safe-ds-type-computer.ts | 1 + .../other/declarations/annotationCalls.ts | 27 +- .../other/declarations/parameters.ts | 31 +- .../validation/other/expressions/calls.ts | 7 +- .../other/expressions/infixOperations.ts | 15 +- src/language/validation/safe-ds-validator.ts | 4 +- src/language/validation/style.ts | 14 +- syntaxes/safe-ds.tmLanguage.json | 12 +- tests/language/partialEvaluation/creator.ts | 49 +- .../testPartialEvaluation.test.ts | 48 +- tests/language/typing/creator.ts | 8 +- .../base cases/boolean literal/main.sdstest | 9 - .../base cases/boolean literals/main.sdstest | 9 + .../base cases/float literal/main.sdstest | 9 - .../base cases/float literals/main.sdstest | 9 + .../base cases/int literal/main.sdstest | 6 - .../base cases/int literals/main.sdstest | 6 + .../base cases/null literal/main.sdstest | 6 - .../base cases/null literals/main.sdstest | 6 + .../main.sdstest | 9 - .../main.sdstest | 9 + .../recursive cases/arguments/main.sdstest | 11 + .../calls/of enum variants/main.sdstest | 37 ++ .../calls/unresolved/main.sdstest | 13 + .../indexed access/on lists/main.sdstest | 24 + .../indexed access/on maps/main.sdstest | 21 + .../indexed access/on other/main.sdstest | 9 + .../infix operations/and/main.sdstest | 27 + .../infix operations/divided by/main.sdstest | 38 ++ .../infix operations/elvis/main.sdstest | 15 + .../infix operations/equals/main.sdstest | 15 + .../greater than or equals/main.sdstest | 41 ++ .../greater than/main.sdstest | 41 ++ .../identical to/main.sdstest | 15 + .../less than or equals/main.sdstest | 41 ++ .../infix operations/less than/main.sdstest | 41 ++ .../infix operations/minus/main.sdstest | 28 + .../infix operations/not equals/main.sdstest | 15 + .../not identical to/main.sdstest | 15 + .../infix operations/or/main.sdstest | 27 + .../infix operations/plus/main.sdstest | 28 + .../infix operations/times/main.sdstest | 28 + .../recursive cases/lists/main.sdstest | 9 + .../recursive cases/maps/main.sdstest | 15 + .../of enum variants/main.sdstest | 16 + .../member accesses/unresolved/main.sdstest | 13 + .../parenthesized expressions/main.sdstest | 11 + .../prefix operations/minus/main.sdstest | 15 + .../prefix operations/not/main.sdstest | 15 + .../template strings/main.sdstest | 12 + .../template string/main.sdstest | 12 - .../skip-old/ToConstantExpressionTest.kt | 413 ++++++++++++++ .../skip-old/callables.sdstest | 42 ++ .../partial evaluation/skip-old/calls.sdstest | 56 ++ .../skip-old/memberAccesses.sdstest | 17 + .../skip-old/references.sdstest | 51 ++ 67 files changed, 2179 insertions(+), 790 deletions(-) create mode 100644 img/safe-ds_file_icon_dark.svg create mode 100644 img/safe-ds_file_icon_light.svg delete mode 100644 img/safe-ds_logo_rounded.png create mode 100644 img/safe-ds_logo_rounded_128x128.png create mode 100644 src/language/partialEvaluation/safe-ds-partial-evaluator.ts delete mode 100644 src/language/partialEvaluation/toConstantExpression.ts delete mode 100644 tests/resources/partial evaluation/base cases/boolean literal/main.sdstest create mode 100644 tests/resources/partial evaluation/base cases/boolean literals/main.sdstest delete mode 100644 tests/resources/partial evaluation/base cases/float literal/main.sdstest create mode 100644 tests/resources/partial evaluation/base cases/float literals/main.sdstest delete mode 100644 tests/resources/partial evaluation/base cases/int literal/main.sdstest create mode 100644 tests/resources/partial evaluation/base cases/int literals/main.sdstest delete mode 100644 tests/resources/partial evaluation/base cases/null literal/main.sdstest create mode 100644 tests/resources/partial evaluation/base cases/null literals/main.sdstest delete mode 100644 tests/resources/partial evaluation/base cases/string literal (without interpolation)/main.sdstest create mode 100644 tests/resources/partial evaluation/base cases/string literals (without interpolation)/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/arguments/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/calls/of enum variants/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/calls/unresolved/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/indexed access/on lists/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/indexed access/on maps/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/indexed access/on other/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/and/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/divided by/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/elvis/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/equals/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/greater than or equals/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/greater than/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/identical to/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/less than or equals/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/less than/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/minus/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/not equals/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/not identical to/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/or/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/plus/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/infix operations/times/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/lists/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/maps/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/member accesses/of enum variants/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/member accesses/unresolved/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/parenthesized expressions/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/prefix operations/minus/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/prefix operations/not/main.sdstest create mode 100644 tests/resources/partial evaluation/recursive cases/template strings/main.sdstest delete mode 100644 tests/resources/partial evaluation/simple recursive cases/template string/main.sdstest create mode 100644 tests/resources/partial evaluation/skip-old/ToConstantExpressionTest.kt create mode 100644 tests/resources/partial evaluation/skip-old/callables.sdstest create mode 100644 tests/resources/partial evaluation/skip-old/calls.sdstest create mode 100644 tests/resources/partial evaluation/skip-old/memberAccesses.sdstest create mode 100644 tests/resources/partial evaluation/skip-old/references.sdstest diff --git a/docs/development/partial-evaluation-testing.md b/docs/development/partial-evaluation-testing.md index 867bcefee..b4a43e1f3 100644 --- a/docs/development/partial-evaluation-testing.md +++ b/docs/development/partial-evaluation-testing.md @@ -22,7 +22,7 @@ partial evaluation test. test marker, the second test comment corresponds to the second test marker, etc. * `// $TEST$ constant equivalence_class `: Assert that all nodes with the same `` get partially evaluated successfully to the same constant expression. - * `// $TEST$ constant serialization `: Assert that the node gets partially evaluated to a constant expression + * `// $TEST$ serialization `: Assert that the node gets partially evaluated to a constant expression that serializes to ``. * `// $TEST$ not constant`: Assert that the node cannot be evaluated to a constant expression. 6. Run the tests. The test runner will automatically pick up the new test. diff --git a/img/safe-ds_file_icon_dark.svg b/img/safe-ds_file_icon_dark.svg new file mode 100644 index 000000000..6ac05fee1 --- /dev/null +++ b/img/safe-ds_file_icon_dark.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/img/safe-ds_file_icon_light.svg b/img/safe-ds_file_icon_light.svg new file mode 100644 index 000000000..12fb6f232 --- /dev/null +++ b/img/safe-ds_file_icon_light.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/img/safe-ds_logo_rounded.png b/img/safe-ds_logo_rounded.png deleted file mode 100644 index cf515d7abe84a94158fea6c0d64cfb96465c31c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9930 zcmeHtc{tSH`}aE(MV3#tFeH0I$lh48jIn3mN!hnB$XKHyg^YdQcTtgLY%P{h8GH65 znXwjw7|ZYI^ZQ=U-_IYv7e|LGWs~VASVNl1d{7_uFKW_^lij)StxJQ6G^t^jQjB=Cc~>JOf|K zs7UTh2$$S+g81jGfEa#8>(1uUmNsZgTK}-9RA|AT&1vm1rw)u?i+71O>}4f@iO{n-f{lI?J|BA#&+>-&BQ$2 zc$U54yCST=9=uWi%6Z(CXjjc!pZv0w192t8NNZQzZirQP;Iq~$^M;y01eaI<|HTi{ z(Wf--?tFW%n^#@_eC{|&Nmav_DZ^ybrJ7zqg}JO;u7Wd#^#c3~4{~<(c&>f*ZGAvly1`VRrai0l#hS=Q#DY^>Zffb^t z{IMPLH*}r}o-YQpgGU4=PqPre4li*uR&LA5kL}lwp{*-T^UDyH6O~svAE-DYAq(;h z3eB%)C(`7?>4okNe7ai`ToW=IzSk!)zA;L>)$2b1-(ACe2-@7V!R%}HC12MXQbY3a zm1whF6;d6dNA{LFQ9x>Mt{^FSQ;=8EP?|$t$Sl4!49Z|gsAGWrn)Gd!CLKB)v%YPZ z5;9F2ix?Vx+;a+2#-wVYZHL@C27-r(*F@y_@wu%BG{|vHtlg0LhXn}2CBDGqV(Pzc zY^7#!YDlmWf2tBb@XheL*6495QbLy|leN(rLu5$xQrgdm8~D%M9XE%jIC#*WDqq`Wy?f*kQM8|T z=I3sZHX2>Pc^)eurercyhw)nJ^@pH)O=i4@of})Z_=?kL-V-@lk#ySo9q`|OAV{qu zJdv0_x8zB)OCAkB>10H5Au~DFd(J>_$=G>q08!x&d-=Ej5;ZHM)TLfpAcO2P7BKvz z(Am&=v~pk*=SLu=m0@Cx90cVclj_I8r?530>n3g6p5G!J5`CTM13`FyP7LvGd7c%W z&BwG8}(z|!v+p)7=W$4_pC1t%(R*kd6&I-^!)w+2NaR(TU1Qh zLeieur_SFia(&v;LIvb#mR0h@chR|Z4H#%dKFuzvHMnIn%H|w;YtdS} zQ~eGE#S|}K9tSK+yvk{ho((BVNl^QA3W}+}j;zYEnl7lxX54+*GzgqhskNZ1t+Ud| zJooNTQU({@xEio_7gyMfep?-EzwS0Ca8ndZ8S(8C>$E33YK&1@1vV04e}0?PTH+a6 zR0P`>hF_I~5XJO1;XS|kwvK^@3{ z4@-k=b`CFa#CgYl6$FAZE?sYN#4TyuqlX~+R=>>9|25_)9z|Lpk;Y>Iv16Uaf!PAP zJNDoh)3-{lDJtN1*;y$7(~WkqqYr#Yu%Tg?$+jqJU~ko7VR?<;pThPXH4rEAV|DgP z+`GXvv}6zz8Cc8PoLNgde)s+fsT++}hFrr&U@4v#H}N@U=7J#)d51+@lLmY$x0mTx5H!a_=Na(-v0IUirAu(-JG zlP5k}S}{g9L$Y}*8ft7urTi-^JPrJHU%0ugnAjNAW-iN{jE+>it~50TR(>Q@uaLI*YuH?w`=QP(dE*Z}rI0`3v7qiGCGoig`# zsn@(Wz82pv;q3|}?Z)Sn4;{T;xB|at+(k;LZ2IwXhmmW@_W1Vb$XN9C3lYD#@e$m= ze~V|{SBWpxCDKh)H=g+1A(^EkM4ihI`=kk#$7B&RGXnE!^$i`Eq^k%!B+uuYq<|%h!d<+GdQE ztG{|V(`jB_P@%-y8?W2X_->(W{3m3aXKM1fqVof7jEt1~O+5#*s1?UV1J;zwsN`|9 zAUxbVxHjWEjIz1GcO&p3KmMJ?7D2R4JUmX?YeHbwjdD^0_kej?G)=VQN65*zI8Pt( z6axk#&SS#1QieICdelAe-ajxLnF?iQS63H)^5TW<9Coo=dOH!E7E$@p2p$>yoEOuoqgY9dF^PIp2l}bW3Pmdm<%S;J}p`(`N;cO#Q ziQX2K71LJQoA-!QP?{nNQp`k%OlfcTA9tANZQQU&Gv*^0Q0Bslil%{c`%E(-hsMp3 z9Zgo|3@AymQ?5YG{n4s$BO9A2f=>je3hs8vyc}VFoYY>B*c_#s;7c3nHMZWixIbY; zZSq6O1I>78)xof?>~NY$uiVsRJrp;tJ9uHs%XI&3dufkr*P^dbH*7EIx6Zxqq%ra# zL*kn%ffY6NHw_y<>2IvQr-hAau4NSm?ONCNiz&_#1=k#+*Ey=jyv8pKhz&i7bsH{% zZVxInoO?N&#{YA9n`QkT>eKwyG`o;?+pXCGF?>ny$M;KqF51;5 z|DYINK*g%Af5Bj~^WB~G46Qw$NqdTz8YOKxpPBr?9jNx=)4PiOMv^zGvu6-{GV+aM za0O?OTLEoq-Zl6+`sCSHCK3MK&)5pn!h@`gyEwi`uOE0E?qqPed@AQIMVyMfiDU3D z)rXa^TgoqrasekP_VN2n=i6f88S!6W7?_0@HGZDb{)@ zqQ0FiS5mV%SEX;e*xh_bIq3Bn!C=jB^`$(+nT3F6;@g>V*N@+f4}Wem-SUa#dP@t7 zXJKI}irzTPbg~hjB?@w{xJ1t~K97y1b6rSv2sJg}o$I~~Q?R`_e^y~kNkly;3E|S$ zX|2naRRqk-x6$a2%WOfOC!y~qd}lhivex@L5S}}d7O^rVa7$nDK-WfgHh$n-9*h`K z3@6dL4yQWoNv{uL&j%a(T)~R^4mIjyN^N9sp3og?;)r8S4RfOO*2eJ>>LNokcmK3h z%9`8(rf)+5i%*f0H5oWMnCjc6k$`J`ZL$_+O5+&aGts_4+8 z9?N$0Q#Q6torDFKSFgNmLig?ipUcOVo8*Pg&S&%x9!v<^DXny0)=ePOyo|pW^XHE_ z{O}`(ZqAKqqToz}Yn7e%r(<(>#fh$^l$Ks#jF_hygw(7u_aXBHf)Dm{qff#*X*}?c zCOgN2{0~j(tc{$(ze>wnwh5_gCFuE-#mV|nZ#1K@4%^DDqvLkVI|%0t&Jrurnu(h5 zqb@QjX|JI@hWA8BnYXzDT9RGWYE_7D>7ZLws>@x@gkC z3h`GGb-jO2m~CsYvhvWELW#riq4TkEPX@mZTKF~w3dvJml)9nnTG4yr=!c8)S$Oc* zoW3aiAW-^=O2ReLs!1M=>*2%%%LZTSjWi8qo6w>V_8ojwLc@94mD?W-S- zDA;Cu5(`{nq2o z4?d>6#Vdz^pUPjK2-H;CPgBA#=*u?uRy3?l*P5&}xNkPyUd~H(Kxa#z@6|L4v92ET zzA6Dfhh~V3k7@SZXFdEAKT+D_GJc@GvOUmYp8o-%pTT)LM>e2PE8LObXH53$sr|))ju%Z$tiwh1h9L1oguOEGJ`RGhP(HZ~cGhyMS9I;_ zmEjVQOi7Og4ZI+*kVQ#Cx>n|3>*m7 z1#wCSelD`!Z7pC%YC{SJUvF*6;}hsw`5iQ8Z0L|pxP_ypM-iJy>sURW$8sBII7nLY z_?^5X+q?~V5Q!h+w{j>^J=1dvo4X{m^%;vRKELJt7gE^%2)>PFE{H<)pXimSSUFYTRLOi+&KJhZl)q|29 zVR=b&SI0&eaeY!#Ok7-LPiV1x?s>^-T|HfWjkgmBx9o)i@OXx$gij;c;U6X8-Ey{h z$FFQdw!Nq8T3K8`1o~bTKFq$QBn|JA`#F707mU0P#8byH1c-}!!PCRrrbU;g`P}F2 zlm@kHL+#NtYF}&ZNXpd5`xON7FZWuk(cg|kMqimVtD+Vx9=#;}+%|%}p|&`<{UA5g z^{k8;;St#>!bBRgdvEmVQiR+c5RtKa`%iTePEj+U9-938W;_$tpHkW*duMcV4ZYgD zEBk5osnlsQG_iTN9z8=lT$Jt8BO(cMTQ@X~`Qv;Z4{K4^btSnao!M>nr^Js&)N-e2 z-8?*oc=Sa^1hTSyJhzKGWk0uVvB}|Wgv=wr1gDTaGTpr8X9JVWVYY>oT%@Aq6?WhN zqsyz@apVc@RUqloO}LEQ-yGROEfT4LY)|P?57j~X9yqhlG<-)BL(BC77nRV~e^@@K z<*%E=d7qdd|NS}puj$Ygov!XpiD$pQWjuV@^EZp2z>z zf*<@KL$op_rA*k>)uLuo7#VV?-;KLhD17x^3z)A7>Y>VIAW4Pt(_i}05YAA4V zc>Bi&2c{ZRz*KzRueUbTsOY!-QQXMlx%(i3IP!F(s-fl`o&$(@E?17KZ&g@;buQ<4 zolf}8@i*}z$+w&&I?myZSxZOMJq~ARt z7+=33Ff$FsSW>`dc1ElC*=@o>+B}K9(=m9_zV?N}4tt04;I^5r1Xi)|f(oLsvm5LG z`PuL8F{N9#Ud*+=djEAF4-4DsK9^HoF5Pe>B`I*vmjfH;OoLs-2o;SeNp*eQ_ZGD- z>Z`Q8vr-gTUR^nwMLo`H)e-I%E1L2BnH2*MnjVGf-!{uqm<}r}w4`PZb?^u!ik|#O zr5%~jwiIPi$zwf~En`dV?s|>?`uSd}TWs*jY!OHW@S=zPMgx-Q>okMW`9=Ls1L^tc ztdjZ{1D3A6`ymSF?_S-z5~D}~v-$3u0pCiUk<+=>rhU9@lCYG=iQ$rEUZ=}ka{=<(%w0-gt5FaHmmTTX+3OK?1PWM zY;f)Nx;xX|&EM9DDoT9wKzbDry3oX^NUYAliQdb%0u?jCC$SLg0}cz*MEnwlYJ_`?@z zmnr+{<7A8|1uQ2MU67#~)}v?a_PNX{KwVkeU2(>7)#cXg4+f1fbrd%q|GeLEz3c98 z%G1-g&c_de>sJ>SGn)nJ@+<AB}nWR z?1NGHMXIF%OICraX19KG^SG0*P1J2XZ&-gWAYCF5Bk%dt2p z$XMd%vQu@YlAl}*XnB@1-@aZqqcL_`zHYq^t}&J)A#Ua+GyXuG1O`Z<@0RZGAfNrg zf|RbWCYD$hrDWfjoUB=TBoocw-!ai`GCeuhgBhN&o z3EnL(T986ikNFv3C*(Zvmv~k((XS}5eItITueb*%O8};t)%1LN+9Qy)!)C#Puhf^$ zIE$_+PSiQ| zhm+IkS$_Nfo&BR%qVB?)BX9ji7cn;`$S=2x7LuzVvV7rAvsF`VX;mM% zV4#@GigzvUDigKvoe=6VlE?rLk9#R9W#N;SaX-=GB7hB5BbgLUb=NlC&;!p#uKW1n*K_=Re0DJdxqI z<#=GQx`jAOFwozDY6$VtfF@>A2I=!k94J@&fCX`hG59=(N$k_OTb3O^Vrr>D3gg1Z zC-?3n7ToIU#r=(i&KiK?wIEe>2$C3DJL}b&ruhIz+@}e{}smCwL+X(**@$ z64NNS(}3+#7F=FtGxn+E2cpWyo^b{wyE^TTO-B70>@HA6S1wP+)b`TCJh}gEr+^Js zR$kd0umR#z12+76O%>I#2cncz(OLocdsLC}riK_@4qpL$jNU44o{<9fB7LI3?;~vR z2~0ja5-^wCD5z6Kpd@^bC>2ok;P^3J2tNcZ_5Y_OrMUBBhxy0|JxZ<2ja&g$!x-H_ zkE;JJac_>&D=XH;@q4rTzq*h^_TXU|ia1Rr4?)bA@DKz-;eQ+dKUxs*Y=bXG29^Jt4HmdiJeaEty7u-EeSx1_#sRLZ$nBb|gu+Ft@` zs$EUwXnZLTdT(Oti5yh)J=(e|Ew9aH%j-rVt4w&Y9Ft05IiczuD& z#cw}sKROg3zSR}bO^6JjM^Uon_~^(?A0(V>`bQ^$405KJezM>Fd2SH@><-9^*{QG} zIrn(YD&|OGBafU(}SNUlp>%Ay&Rdy@hfHAc-o}gj5}xw2tV>o zCxO5K-WUT}1M{|Y698!Q{Y_sGkmP!2ohs$Gkn_Jk=p;li|Gzjb4!U#^evYfyJ|Yz! zxlYlHL1gA$zD#D)Qc`L;d?z1a?M! zTEpbuu5$QN0(go72GB)%6a-yW#+`y*LMs1l{O^MN*BJh<4nknMDpT)YKapZqoX&Yl zgdP<=VJ-iaNCn}9Y^$=P^A%{0H6gVqGTSN+@bRe>Lo>_+DVIs7(4$VgN}ZcCZ3_yD zZ>Zo^tqS;=&Pjek!qvrhrDwzT1@6 zBRE`~_aJ!Ps>ARdFi>Bt#k{2tJSviV#z?`T$(^a=b_2}y-Q_p237*$)oR65Tso~Z* z86_suA^i?`EmbM$X5!GY7{PnWZ+rD~zm%&>1|bn58fIxp5R>-ITm=;T5PqNlfV?zX zxyNBf7kkj^pvz`ib9Q1eGu@`GyIFWUz0qR?Y+t@PI44*5| z9x{6T_ymLv+JAa1tAKxcp5`wKTRP($xxO3J-x+5KU~Xo|u)JNJ#u<_JJ)MWqC&T5+ zCPbpzFGWTHX!cvq@M@}x7{-{n*#dyF^nJW*a1!+rMcZDb&s^*`Wq?MLeqNU$Jxf(U zAuWX}u1RcxSzcF?>nj&oY&;K(1;oNziz9P$0r-xZy5BL-QnJW0HCfQVvBj)qIkvz9 z;P-X&Nz+dy0geW5DV%A%*iIfVWZO{Qy?3Z}SeQS4}IA&CoJN6|k>S5Opn|TY9m&O4;&S)ohF0hIQE(=w-zp$oJEbB}xo)@J+p+|psf`oYb#K)iF1 z_0aoEVRT@US3$7=C`GW57pma{#U$0mA{Y(IEfQd&o2Q-mZ8wM=OM3cl6*t<7V?-)M z?uCLHl0I6bQPh)W#=AJaUQob+xvs|%25Lu)srlmEz1$73{)X1t0@|Uv6%)g=DaK-Y z$>h)_RyriMXJbqH(dH$Uw&+<wAsr`QYJ_HfT>f~FB-OV0};4wdX`v*n|+9N*d zplD+<(7H^FZFq4YBak1zi<=q>`JGYleR2bW>YbzX?7m2Oh^M{hKoLGUI+SUld8ZT3 zL(0j)YGng=H-;9iTX?FYr>!=BEP_7MAy0TSz~O0kUl%t6qf*ls!4;_-M}mqJtDy}( z8`zU=P$3Xgg7Mk{l@IVX1GUJb(?CR7q_;L3SFb-PuXuSAIk!n_wYvPqUJWcf0IbuB z`}x{Ps^W~Bj7CB_i5C|!wt|Sqk6`#iJXvqJt zyT(jRy%Dq*<-Z@cvin4J6M~$DlZo}kUBHf}iPtbukzvwcC+u<*uZAZSTl_8Skq?cW z^23;d(w1|>AL@U6%+GtVhYN`<)&Vs`x~bQ#4Y9@V&TfWF> qUMA$Eplf4s6y}O9Olza^_*7SigiP!Bek~|kfRO5XYBe|QBmW0OIE^O& diff --git a/img/safe-ds_logo_rounded_128x128.png b/img/safe-ds_logo_rounded_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..acbf30a0a0059dce6c89d59df712aed094582f51 GIT binary patch literal 2947 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 96eb9c325..f737f7199 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "galleryBanner": { "color": "#e3e9e9" }, - "icon": "img/safe-ds_logo_rounded.png", + "icon": "img/safe-ds_logo_rounded_128x128.png", "homepage": "https://dsl.safeds.com", "qna": "https://github.com/orgs/Safe-DS/discussions", "bugs": { @@ -60,7 +60,11 @@ ".sdsstub", ".sdstest" ], - "configuration": "./language-configuration.json" + "configuration": "./language-configuration.json", + "icon": { + "light": "img/safe-ds_file_icon_light.svg", + "dark": "img/safe-ds_file_icon_dark.svg" + } } ], "grammars": [ diff --git a/src/language/builtins/safe-ds-annotations.ts b/src/language/builtins/safe-ds-annotations.ts index cbdf5f509..b38ce1e14 100644 --- a/src/language/builtins/safe-ds-annotations.ts +++ b/src/language/builtins/safe-ds-annotations.ts @@ -5,8 +5,8 @@ import { resourceNameToUri } from '../../helpers/resources.js'; import { URI } from 'langium'; import { SafeDsServices } from '../safe-ds-module.js'; import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js'; -import { toConstantExpression } from '../partialEvaluation/toConstantExpression.js'; -import { ConstantExpression, ConstantString } from '../partialEvaluation/model.js'; +import { EvaluatedNode, StringConstant } from '../partialEvaluation/model.js'; +import { SafeDsPartialEvaluator } from '../partialEvaluation/safe-ds-partial-evaluator.js'; const ANNOTATION_USAGE_URI = resourceNameToUri('builtins/safeds/lang/annotationUsage.sdsstub'); const CODE_GENERATION_URI = resourceNameToUri('builtins/safeds/lang/codeGeneration.sdsstub'); @@ -15,11 +15,13 @@ const MATURITY_URI = resourceNameToUri('builtins/safeds/lang/maturity.sdsstub'); export class SafeDsAnnotations extends SafeDsModuleMembers { private readonly nodeMapper: SafeDsNodeMapper; + private readonly partialEvaluator: SafeDsPartialEvaluator; constructor(services: SafeDsServices) { super(services); this.nodeMapper = services.helpers.NodeMapper; + this.partialEvaluator = services.evaluation.PartialEvaluator; } isDeprecated(node: SdsAnnotatedObject | undefined): boolean { @@ -48,7 +50,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { getPythonModule(node: SdsModule | undefined): string | undefined { const value = this.getArgumentValue(node, this.PythonModule, 'qualifiedName'); - if (value instanceof ConstantString) { + if (value instanceof StringConstant) { return value.value; } else { return undefined; @@ -61,7 +63,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { getPythonName(node: SdsAnnotatedObject | undefined): string | undefined { const value = this.getArgumentValue(node, this.PythonName, 'name'); - if (value instanceof ConstantString) { + if (value instanceof StringConstant) { return value.value; } else { return undefined; @@ -92,11 +94,11 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { node: SdsAnnotatedObject | undefined, annotation: SdsAnnotation | undefined, parameterName: string, - ): ConstantExpression | undefined { + ): EvaluatedNode { const annotationCall = findFirstAnnotationCallOf(node, annotation); - const expression = argumentsOrEmpty(annotationCall).find( + const argumentValue = argumentsOrEmpty(annotationCall).find( (it) => this.nodeMapper.argumentToParameterOrUndefined(it)?.name === parameterName, )?.value; - return toConstantExpression(expression); + return this.partialEvaluator.evaluate(argumentValue); } } diff --git a/src/language/partialEvaluation/model.ts b/src/language/partialEvaluation/model.ts index 03a0c81bf..ec4c2472d 100644 --- a/src/language/partialEvaluation/model.ts +++ b/src/language/partialEvaluation/model.ts @@ -8,260 +8,381 @@ import { SdsReference, SdsResult, } from '../generated/ast.js'; +import { stream } from 'langium'; +import { parametersOrEmpty } from '../helpers/nodeProperties.js'; +import { isEmpty } from 'radash'; + +export type ParameterSubstitutions = Map; +export type ResultSubstitutions = Map; /* c8 ignore start */ -export type ParameterSubstitutions = Map; -export type ResultSubstitutions = Map; -export abstract class SimplifiedExpression { +/** + * A node that has been partially evaluated. + */ +export abstract class EvaluatedNode { + /** + * Whether the node could be evaluated completely. + */ + abstract readonly isFullyEvaluated: boolean; + + /** + * Returns whether the node is equal to another node. + */ + abstract equals(other: EvaluatedNode): boolean; + + /** + * Returns the string representation of the node. + */ + abstract toString(): string; + /** - * Removes any unnecessary containers from the expression. + * Removes any unnecessary containers from the node. */ - unwrap(): SimplifiedExpression { + unwrap(): EvaluatedNode { return this; } } -export abstract class IntermediateExpression extends SimplifiedExpression {} +// ------------------------------------------------------------------------------------------------- +// Constants +// ------------------------------------------------------------------------------------------------- -export abstract class IntermediateCallable extends IntermediateExpression {} +export abstract class Constant extends EvaluatedNode { + override readonly isFullyEvaluated: boolean = true; -export class IntermediateBlockLambda extends IntermediateCallable { - constructor( - readonly parameters: SdsParameter[], - readonly results: SdsBlockLambdaResult[], - readonly substitutionsOnCreation: ParameterSubstitutions, - ) { - super(); + /** + * Returns the string representation of the constant if it occurs in a string template. + */ + toInterpolationString(): string { + return this.toString(); } } -export class IntermediateExpressionLambda extends IntermediateCallable { - constructor( - readonly parameters: SdsParameter[], - readonly result: SdsExpression, - readonly substitutionsOnCreation: ParameterSubstitutions, - ) { +export class BooleanConstant extends Constant { + constructor(readonly value: boolean) { super(); } + + override equals(other: EvaluatedNode): boolean { + return other instanceof BooleanConstant && this.value === other.value; + } + + override toString(): string { + return this.value.toString(); + } } -export class IntermediateStep extends IntermediateCallable { - constructor( - readonly parameters: SdsParameter[], - readonly results: SdsResult[], - ) { +export abstract class NumberConstant extends Constant { + abstract readonly value: number | bigint; +} + +export class FloatConstant extends NumberConstant { + constructor(readonly value: number) { super(); } + + override equals(other: EvaluatedNode): boolean { + return other instanceof FloatConstant && this.value === other.value; + } + + override toString(): string { + return this.value.toString(); + } } -export class IntermediateRecord extends IntermediateExpression { - constructor(readonly resultSubstitutions: ResultSubstitutions) { +export class IntConstant extends NumberConstant { + constructor(readonly value: bigint) { super(); } - getSubstitutionByReferenceOrNull(reference: SdsReference): SimplifiedExpression | null { - const referencedDeclaration = reference.target; - if (!isSdsAbstractResult(referencedDeclaration)) { - return null; - } - - return this.resultSubstitutions.get(referencedDeclaration) ?? null; + override equals(other: EvaluatedNode): boolean { + return other instanceof IntConstant && this.value === other.value; } - getSubstitutionByIndexOrNull(index: number | null): SimplifiedExpression | null { - if (index === null) { - return null; - } - return Array.from(this.resultSubstitutions.values())[index] ?? null; + override toString(): string { + return this.value.toString(); } +} - /** - * If the record contains exactly one substitution its value is returned. Otherwise, it returns `this`. - */ - override unwrap(): SimplifiedExpression { - if (this.resultSubstitutions.size === 1) { - return this.resultSubstitutions.values().next().value; - } else { - return this; - } +class NullConstantClass extends Constant { + override equals(other: EvaluatedNode): boolean { + return other instanceof NullConstantClass; } override toString(): string { - const entryString = Array.from(this.resultSubstitutions, ([result, value]) => `${result.name}=${value}`).join( - ', ', - ); - return `{${entryString}}`; + return 'null'; } } -export class IntermediateVariadicArguments extends IntermediateExpression { - constructor(readonly arguments_: (SimplifiedExpression | null)[]) { +export const NullConstant = new NullConstantClass(); + +export class StringConstant extends Constant { + constructor(readonly value: string) { super(); } - getArgumentByIndexOrNull(index: number | null): SimplifiedExpression | null { - if (index === null) { - return null; - } - return this.arguments_[index] ?? null; + override equals(other: EvaluatedNode): boolean { + return other instanceof StringConstant && this.value === other.value; + } + + override toString(): string { + return `"${this.value}"`; + } + + override toInterpolationString(): string { + return this.value; } } -export abstract class ConstantExpression extends SimplifiedExpression { - abstract equals(other: ConstantExpression): boolean; +export const isConstant = (node: EvaluatedNode): node is Constant => { + return node instanceof Constant; +}; - abstract override toString(): string; +// ------------------------------------------------------------------------------------------------- +// Closures +// ------------------------------------------------------------------------------------------------- - /** - * Returns the string representation of the constant expression if it occurs in a string template. - */ - toInterpolationString(): string { - return this.toString(); - } +export abstract class Closure extends EvaluatedNode { + override readonly isFullyEvaluated: boolean = false; + abstract readonly substitutionsOnCreation: ParameterSubstitutions; } -export class ConstantBoolean extends ConstantExpression { - constructor(readonly value: boolean) { +export class BlockLambdaClosure extends Closure { + constructor( + override readonly substitutionsOnCreation: ParameterSubstitutions, + readonly results: SdsBlockLambdaResult[], + ) { super(); } - equals(other: ConstantExpression): boolean { - return other instanceof ConstantBoolean && this.value === other.value; + override equals(_other: EvaluatedNode): boolean { + // TODO + return false; } - toString(): string { - return this.value.toString(); + override toString(): string { + // TODO + return ''; } } -export class ConstantEnumVariant extends ConstantExpression { - constructor(readonly value: SdsEnumVariant) { +export class ExpressionLambdaClosure extends Closure { + constructor( + override readonly substitutionsOnCreation: ParameterSubstitutions, + readonly result: SdsExpression, + ) { super(); } - equals(other: ConstantExpression): boolean { - return other instanceof ConstantEnumVariant && this.value === other.value; + override equals(_other: EvaluatedNode): boolean { + // TODO + return false; } - toString(): string { - return this.value.name; + override toString(): string { + // TODO + return ''; } } -export abstract class ConstantNumber extends ConstantExpression {} +export class SegmentClosure extends Closure { + override readonly substitutionsOnCreation = new Map(); -export class ConstantFloat extends ConstantNumber { - constructor(readonly value: number) { + constructor(readonly results: SdsResult[]) { super(); } - equals(other: ConstantExpression): boolean { - return other instanceof ConstantFloat && this.value === other.value; + override equals(_other: EvaluatedNode): boolean { + // TODO + return false; } - toString(): string { - return this.value.toString(); + override toString(): string { + // TODO + return ''; } } -export class ConstantInt extends ConstantNumber { - constructor(readonly value: bigint) { +// ------------------------------------------------------------------------------------------------- +// Other +// ------------------------------------------------------------------------------------------------- + +export class EvaluatedEnumVariant extends EvaluatedNode { + constructor( + readonly variant: SdsEnumVariant, + readonly args: ParameterSubstitutions | undefined, + ) { super(); } - equals(other: ConstantExpression): boolean { - return other instanceof ConstantInt && this.value === other.value; + readonly hasBeenInstantiated = this.args !== undefined; + + override readonly isFullyEvaluated: boolean = + isEmpty(parametersOrEmpty(this.variant)) || + (this.args !== undefined && stream(this.args.values()).every(isFullyEvaluated)); + + override equals(other: EvaluatedNode): boolean { + return other instanceof EvaluatedEnumVariant && this.variant === other.variant; } - toString(): string { - return this.value.toString(); + override toString(): string { + if (!this.args) { + return this.variant.name; + } else { + return `${this.variant.name}(${Array.from(this.args.values()).join(', ')})`; + } } } -export class ConstantList extends ConstantExpression { - constructor(readonly elements: ConstantExpression[]) { +export class EvaluatedList extends EvaluatedNode { + constructor(readonly elements: EvaluatedNode[]) { super(); } - equals(other: ConstantExpression): boolean { - return other instanceof ConstantList && this.elements.every((e, i) => e.equals(other.elements[i])); + override readonly isFullyEvaluated: boolean = this.elements.every(isFullyEvaluated); + + getElementByIndex(index: number | undefined): EvaluatedNode { + if (index === undefined) { + return UnknownEvaluatedNode; + } + return this.elements[index] ?? UnknownEvaluatedNode; } - toString(): string { + override equals(other: EvaluatedNode): boolean { + return other instanceof EvaluatedList && this.elements.every((e, i) => e.equals(other.elements[i])); + } + + override toString(): string { return `[${this.elements.join(', ')}]`; } } -export class ConstantMap extends ConstantExpression { - constructor(readonly entries: ConstantMapEntry[]) { +export class EvaluatedMap extends EvaluatedNode { + constructor(readonly entries: EvaluatedMapEntry[]) { super(); } - equals(other: ConstantExpression): boolean { - return other instanceof ConstantMap && this.entries.every((e, i) => e.equals(other.entries[i])); + override readonly isFullyEvaluated: boolean = this.entries.every(isFullyEvaluated); + + getLastValueForKey(key: EvaluatedNode): EvaluatedNode { + return this.entries.findLast((it) => it.key.equals(key))?.value ?? UnknownEvaluatedNode; + } + + override equals(other: EvaluatedNode): boolean { + return other instanceof EvaluatedMap && this.entries.every((e, i) => e.equals(other.entries[i])); } - toString(): string { + override toString(): string { return `{${this.entries.join(', ')}}`; } } -export class ConstantMapEntry { +export class EvaluatedMapEntry extends EvaluatedNode { constructor( - readonly key: ConstantExpression, - readonly value: ConstantExpression, - ) {} + readonly key: EvaluatedNode, + readonly value: EvaluatedNode, + ) { + super(); + } + + override readonly isFullyEvaluated: boolean = this.key.isFullyEvaluated && this.value.isFullyEvaluated; + + override equals(other: EvaluatedNode): boolean { + if (other === this) { + return true; + } + + if (!(other instanceof EvaluatedMapEntry)) { + return false; + } - equals(other: ConstantMapEntry): boolean { return this.key.equals(other.key) && this.value.equals(other.value); } - toString(): string { + override toString(): string { return `${this.key}: ${this.value}`; } } -class ConstantNullClass extends ConstantExpression { - equals(other: ConstantExpression): boolean { - return other instanceof ConstantNullClass; +export class EvaluatedNamedTuple extends EvaluatedNode { + constructor(readonly entries: ResultSubstitutions) { + super(); } - toString(): string { - return 'null'; - } -} + override readonly isFullyEvaluated: boolean = stream(this.entries.values()).every(isFullyEvaluated); -export const ConstantNull = new ConstantNullClass(); + getSubstitutionByReference(reference: SdsReference): EvaluatedNode | undefined { + const referencedDeclaration = reference.target; + if (!isSdsAbstractResult(referencedDeclaration)) { + return undefined; + } -export class ConstantString extends ConstantExpression { - constructor(readonly value: string) { - super(); + return this.entries.get(referencedDeclaration) ?? undefined; } - equals(other: ConstantExpression): boolean { - return other instanceof ConstantString && this.value === other.value; + getSubstitutionByIndex(index: number | undefined): EvaluatedNode | undefined { + if (index === undefined) { + return undefined; + } + return Array.from(this.entries.values())[index] ?? undefined; } - toString(): string { - return `"${this.value}"`; + /** + * If the record contains exactly one substitution its value is returned. Otherwise, it returns `this`. + */ + override unwrap(): EvaluatedNode { + if (this.entries.size === 1) { + return this.entries.values().next().value; + } else { + return this; + } } - override toInterpolationString(): string { - return this.value; + override equals(other: EvaluatedNode): boolean { + if (other === this) { + return true; + } + + if (!(other instanceof EvaluatedNamedTuple)) { + return false; + } + + if (other.entries.size !== this.entries.size) { + return false; + } + + // TODO + + return true; + } + + override toString(): string { + const entryString = Array.from(this.entries, ([result, value]) => `${result.name}=${value}`).join(', '); + return `{${entryString}}`; } } -class UnknownValueClass extends ConstantExpression { - override equals(other: ConstantExpression): boolean { - return other instanceof UnknownValueClass; +class UnknownEvaluatedNodeClass extends EvaluatedNode { + override readonly isFullyEvaluated: boolean = false; + + override equals(other: EvaluatedNode): boolean { + return other instanceof UnknownEvaluatedNodeClass; } - toString(): string { + override toString(): string { return '$unknown'; } } -export const UnknownValue = new UnknownValueClass(); +export const UnknownEvaluatedNode = new UnknownEvaluatedNodeClass(); + +// ------------------------------------------------------------------------------------------------- +// Helpers +// ------------------------------------------------------------------------------------------------- + +const isFullyEvaluated = (node: EvaluatedNode): boolean => { + return node.isFullyEvaluated; +}; /* c8 ignore stop */ diff --git a/src/language/partialEvaluation/safe-ds-partial-evaluator.ts b/src/language/partialEvaluation/safe-ds-partial-evaluator.ts new file mode 100644 index 000000000..ce3142b4a --- /dev/null +++ b/src/language/partialEvaluation/safe-ds-partial-evaluator.ts @@ -0,0 +1,523 @@ +import { SafeDsServices } from '../safe-ds-module.js'; +import { AstNode, AstNodeLocator, getDocument, WorkspaceCache } from 'langium'; +import { + BooleanConstant, + EvaluatedEnumVariant, + EvaluatedList, + EvaluatedMap, + EvaluatedMapEntry, + EvaluatedNode, + FloatConstant, + IntConstant, + isConstant, + NullConstant, + NumberConstant, + ParameterSubstitutions, + StringConstant, + UnknownEvaluatedNode, +} from './model.js'; +import { + isSdsArgument, + isSdsBlockLambda, + isSdsBoolean, + isSdsCall, + isSdsEnumVariant, + isSdsExpression, + isSdsExpressionLambda, + isSdsFloat, + isSdsIndexedAccess, + isSdsInfixOperation, + isSdsInt, + isSdsList, + isSdsMap, + isSdsMemberAccess, + isSdsNull, + isSdsParenthesizedExpression, + isSdsPrefixOperation, + isSdsReference, + isSdsString, + isSdsTemplateString, + isSdsTemplateStringEnd, + isSdsTemplateStringInner, + isSdsTemplateStringStart, + SdsBlockLambda, + SdsCall, + SdsExpression, + SdsExpressionLambda, + SdsIndexedAccess, + SdsInfixOperation, + SdsList, + SdsMap, + SdsMemberAccess, + SdsPrefixOperation, + SdsReference, + SdsTemplateString, +} from '../generated/ast.js'; +import { isEmpty } from 'radash'; +import { argumentsOrEmpty, parametersOrEmpty } from '../helpers/nodeProperties.js'; +import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js'; + +export class SafeDsPartialEvaluator { + private readonly astNodeLocator: AstNodeLocator; + private readonly nodeMapper: SafeDsNodeMapper; + + private readonly cache: WorkspaceCache; + + constructor(services: SafeDsServices) { + this.astNodeLocator = services.workspace.AstNodeLocator; + this.nodeMapper = services.helpers.NodeMapper; + + this.cache = new WorkspaceCache(services.shared); + } + + evaluate(node: AstNode | undefined): EvaluatedNode { + return this.cachedDoEvaluate(node, NO_SUBSTITUTIONS)?.unwrap(); + } + + private cachedDoEvaluate(node: AstNode | undefined, substitutions: ParameterSubstitutions): EvaluatedNode { + // Only expressions can be evaluated at the moment + if (!isSdsExpression(node)) { + return UnknownEvaluatedNode; + } + + // Try to evaluate the node without parameter substitutions and cache the result + const documentUri = getDocument(node).uri.toString(); + const nodePath = this.astNodeLocator.getAstNodePath(node); + const key = `${documentUri}~${nodePath}`; + const resultWithoutSubstitutions = this.cache.get(key, () => this.doEvaluate(node, NO_SUBSTITUTIONS)); + if (resultWithoutSubstitutions.isFullyEvaluated || isEmpty(substitutions)) { + return resultWithoutSubstitutions; + } /* c8 ignore start */ else { + // Try again with parameter substitutions but don't cache the result + return this.doEvaluate(node, substitutions); + } /* c8 ignore stop */ + } + + private doEvaluate(node: SdsExpression, substitutions: ParameterSubstitutions): EvaluatedNode { + // Base cases + if (isSdsBoolean(node)) { + return new BooleanConstant(node.value); + } else if (isSdsFloat(node)) { + return new FloatConstant(node.value); + } else if (isSdsInt(node)) { + return new IntConstant(BigInt(node.value)); + } else if (isSdsNull(node)) { + return NullConstant; + } else if (isSdsString(node)) { + return new StringConstant(node.value); + } else if (isSdsTemplateStringStart(node)) { + return new StringConstant(node.value); + } else if (isSdsTemplateStringInner(node)) { + return new StringConstant(node.value); + } else if (isSdsTemplateStringEnd(node)) { + return new StringConstant(node.value); + } else if (isSdsBlockLambda(node)) { + return this.evaluateBlockLambda(node, substitutions); + } else if (isSdsExpressionLambda(node)) { + return this.evaluateExpressionLambda(node, substitutions); + } + + // Recursive cases + else if (isSdsArgument(node)) { + return this.cachedDoEvaluate(node.value, substitutions); + } else if (isSdsCall(node)) { + return this.evaluateCall(node, substitutions); + } else if (isSdsIndexedAccess(node)) { + return this.evaluateIndexedAccess(node, substitutions); + } else if (isSdsInfixOperation(node)) { + return this.evaluateInfixOperation(node, substitutions); + } else if (isSdsList(node)) { + return this.evaluateList(node, substitutions); + } else if (isSdsMap(node)) { + return this.evaluateMap(node, substitutions); + } else if (isSdsMemberAccess(node)) { + return this.evaluateMemberAccess(node, substitutions); + } else if (isSdsParenthesizedExpression(node)) { + return this.cachedDoEvaluate(node.expression, substitutions); + } else if (isSdsPrefixOperation(node)) { + return this.evaluatePrefixOperation(node, substitutions); + } else if (isSdsReference(node)) { + return this.evaluateReference(node, substitutions); + } else if (isSdsTemplateString(node)) { + return this.evaluateTemplateString(node, substitutions); + } /* c8 ignore start */ else { + return UnknownEvaluatedNode; + } /* c8 ignore stop */ + } + + private evaluateBlockLambda(_node: SdsBlockLambda, _substitutions: ParameterSubstitutions): EvaluatedNode { + // return when { + // callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateBlockLambda( + // parameters = parametersOrEmpty(), + // results = blockLambdaResultsOrEmpty(), + // substitutionsOnCreation = substitutions + // ) + // else -> undefined + // } + return UnknownEvaluatedNode; + } + + private evaluateExpressionLambda( + _node: SdsExpressionLambda, + _substitutions: ParameterSubstitutions, + ): EvaluatedNode { + // return when { + // callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateExpressionLambda( + // parameters = parametersOrEmpty(), + // result = result, + // substitutionsOnCreation = substitutions + // ) + // else -> undefined + // } + return UnknownEvaluatedNode; + } + + private evaluateInfixOperation(node: SdsInfixOperation, substitutions: ParameterSubstitutions): EvaluatedNode { + // By design none of the operators are short-circuited + const evaluatedLeft = this.cachedDoEvaluate(node.leftOperand, substitutions); + if (evaluatedLeft === UnknownEvaluatedNode) { + return UnknownEvaluatedNode; + } + + const evaluatedRight = this.cachedDoEvaluate(node.rightOperand, substitutions); + if (evaluatedRight === UnknownEvaluatedNode) { + return UnknownEvaluatedNode; + } + + switch (node.operator) { + case 'or': + return this.evaluateLogicalOp( + evaluatedLeft, + (leftOperand, rightOperand) => leftOperand || rightOperand, + evaluatedRight, + ); + case 'and': + return this.evaluateLogicalOp( + evaluatedLeft, + (leftOperand, rightOperand) => leftOperand && rightOperand, + evaluatedRight, + ); + case '==': + case '===': + return new BooleanConstant(evaluatedLeft.equals(evaluatedRight)); + case '!=': + case '!==': + return new BooleanConstant(!evaluatedLeft.equals(evaluatedRight)); + case '<': + return this.evaluateComparisonOp( + evaluatedLeft, + (leftOperand, rightOperand) => leftOperand < rightOperand, + evaluatedRight, + ); + case '<=': + return this.evaluateComparisonOp( + evaluatedLeft, + (leftOperand, rightOperand) => leftOperand <= rightOperand, + evaluatedRight, + ); + case '>=': + return this.evaluateComparisonOp( + evaluatedLeft, + (leftOperand, rightOperand) => leftOperand >= rightOperand, + evaluatedRight, + ); + case '>': + return this.evaluateComparisonOp( + evaluatedLeft, + (leftOperand, rightOperand) => leftOperand > rightOperand, + evaluatedRight, + ); + case '+': + return this.evaluateArithmeticOp( + evaluatedLeft, + (leftOperand, rightOperand) => leftOperand + rightOperand, + (leftOperand, rightOperand) => leftOperand + rightOperand, + evaluatedRight, + ); + case '-': + return this.evaluateArithmeticOp( + evaluatedLeft, + (leftOperand, rightOperand) => leftOperand - rightOperand, + (leftOperand, rightOperand) => leftOperand - rightOperand, + evaluatedRight, + ); + case '*': + return this.evaluateArithmeticOp( + evaluatedLeft, + (leftOperand, rightOperand) => leftOperand * rightOperand, + (leftOperand, rightOperand) => leftOperand * rightOperand, + evaluatedRight, + ); + case '/': + // Division by zero + if (zeroes.some((it) => it.equals(evaluatedRight))) { + return UnknownEvaluatedNode; + } + + return this.evaluateArithmeticOp( + evaluatedLeft, + (leftOperand, rightOperand) => leftOperand / rightOperand, + (leftOperand, rightOperand) => leftOperand / rightOperand, + evaluatedRight, + ); + case '?:': + if (evaluatedLeft === NullConstant) { + return evaluatedRight; + } else { + return evaluatedLeft; + } + + /* c8 ignore next 2 */ + default: + return UnknownEvaluatedNode; + } + } + + private evaluateLogicalOp( + leftOperand: EvaluatedNode, + operation: (leftOperand: boolean, rightOperand: boolean) => boolean, + rightOperand: EvaluatedNode, + ): EvaluatedNode { + if (leftOperand instanceof BooleanConstant && rightOperand instanceof BooleanConstant) { + return new BooleanConstant(operation(leftOperand.value, rightOperand.value)); + } + + return UnknownEvaluatedNode; + } + + private evaluateComparisonOp( + leftOperand: EvaluatedNode, + operation: (leftOperand: number | bigint, rightOperand: number | bigint) => boolean, + rightOperand: EvaluatedNode, + ): EvaluatedNode { + if (leftOperand instanceof NumberConstant && rightOperand instanceof NumberConstant) { + return new BooleanConstant(operation(leftOperand.value, rightOperand.value)); + } + + return UnknownEvaluatedNode; + } + + private evaluateArithmeticOp( + leftOperand: EvaluatedNode, + intOperation: (leftOperand: bigint, rightOperand: bigint) => bigint, + floatOperation: (leftOperand: number, rightOperand: number) => number, + rightOperand: EvaluatedNode, + ): EvaluatedNode { + if (leftOperand instanceof IntConstant && rightOperand instanceof IntConstant) { + return new IntConstant(intOperation(leftOperand.value, rightOperand.value)); + } else if (leftOperand instanceof NumberConstant && rightOperand instanceof NumberConstant) { + return new FloatConstant(floatOperation(Number(leftOperand.value), Number(rightOperand.value))); + } + + return UnknownEvaluatedNode; + } + + private evaluateList(node: SdsList, substitutions: ParameterSubstitutions): EvaluatedNode { + // TODO: if any entry has side effects, return UnknownEvaluatedNode + return new EvaluatedList(node.elements.map((it) => this.cachedDoEvaluate(it, substitutions))); + } + + private evaluateMap(node: SdsMap, substitutions: ParameterSubstitutions): EvaluatedNode { + // TODO: if any entry has side effects, return UnknownEvaluatedNode + return new EvaluatedMap( + node.entries.map((it) => { + const key = this.cachedDoEvaluate(it.key, substitutions); + const value = this.cachedDoEvaluate(it.value, substitutions); + return new EvaluatedMapEntry(key, value); + }), + ); + } + + private evaluatePrefixOperation(node: SdsPrefixOperation, substitutions: ParameterSubstitutions): EvaluatedNode { + const evaluatedOperand = this.cachedDoEvaluate(node.operand, substitutions); + if (evaluatedOperand === UnknownEvaluatedNode) { + return UnknownEvaluatedNode; + } + + if (node.operator === 'not') { + if (evaluatedOperand instanceof BooleanConstant) { + return new BooleanConstant(!evaluatedOperand.value); + } + } else if (node.operator === '-') { + if (evaluatedOperand instanceof FloatConstant) { + return new FloatConstant(-evaluatedOperand.value); + } else if (evaluatedOperand instanceof IntConstant) { + return new IntConstant(-evaluatedOperand.value); + } + } + + return UnknownEvaluatedNode; + } + + private evaluateTemplateString(node: SdsTemplateString, substitutions: ParameterSubstitutions): EvaluatedNode { + const expressions = node.expressions.map((it) => this.cachedDoEvaluate(it, substitutions)); + if (expressions.every(isConstant)) { + return new StringConstant(expressions.map((it) => it.toInterpolationString()).join('')); + } + + return UnknownEvaluatedNode; + } + + private evaluateCall(node: SdsCall, substitutions: ParameterSubstitutions): EvaluatedNode { + const receiver = this.cachedDoEvaluate(node.receiver, substitutions).unwrap(); + + if (receiver instanceof EvaluatedEnumVariant) { + // The enum variant has already been instantiated + if (receiver.hasBeenInstantiated) { + return UnknownEvaluatedNode; + } + + // Store default values for all parameters + const args = new Map( + parametersOrEmpty(receiver.variant).map((it) => { + return [it, this.cachedDoEvaluate(it.defaultValue, NO_SUBSTITUTIONS)]; + }), + ); + + // Override default values with the actual arguments + argumentsOrEmpty(node).forEach((it) => { + const parameter = this.nodeMapper.argumentToParameterOrUndefined(it); + if (parameter && args.has(parameter)) { + args.set(parameter, this.cachedDoEvaluate(it.value, substitutions)); + } + }); + + return new EvaluatedEnumVariant(receiver.variant, args); + } + + // val simpleReceiver = evaluateReceiver(substitutions) ?: return undefined + // val newSubstitutions = buildNewSubstitutions(simpleReceiver, substitutions) + // + // return when (simpleReceiver) { + // is SdsIntermediateBlockLambda -> { + // SdsIntermediateRecord( + // simpleReceiver.results.map { + // it to it.evaluateAssignee(newSubstitutions) + // } + // ) + // } + // is SdsIntermediateExpressionLambda -> simpleReceiver.result.evaluate(newSubstitutions) + // is SdsIntermediateStep -> { + // SdsIntermediateRecord( + // simpleReceiver.results.map { + // it to it.uniqueYieldOrNull()?.evaluateAssignee(newSubstitutions) + // } + // ) + // } + // } + return UnknownEvaluatedNode; + } + + // private fun SdsCall.buildNewSubstitutions( + // simpleReceiver: SdsIntermediateCallable, + // oldSubstitutions: ParameterSubstitutions + // ): ParameterSubstitutions { + // + // val substitutionsOnCreation = when (simpleReceiver) { + // is SdsIntermediateBlockLambda -> simpleReceiver.substitutionsOnCreation + // is SdsIntermediateExpressionLambda -> simpleReceiver.substitutionsOnCreation + // else -> emptyMap() + // } + // + // val substitutionsOnCall = argumentsOrEmpty() + // .groupBy { it.parameterOrNull() } + // .mapValues { (parameter, arguments) -> + // when { + // parameter == undefined -> undefined + // parameter.isVariadic -> SdsIntermediateVariadicArguments( + // arguments.map { it.evaluate(oldSubstitutions) } + // ) + // else -> arguments.uniqueOrNull()?.evaluate(oldSubstitutions) + // } + // } + // + // return buildMap { + // putAll(substitutionsOnCreation) + // substitutionsOnCall.entries.forEach { (parameter, argument) -> + // if (parameter != undefined) { + // put(parameter, argument) + // } + // } + // } + // } + + private evaluateIndexedAccess(node: SdsIndexedAccess, substitutions: ParameterSubstitutions): EvaluatedNode { + const receiver = this.cachedDoEvaluate(node.receiver, substitutions).unwrap(); + + if (receiver instanceof EvaluatedList) { + const index = this.cachedDoEvaluate(node.index, substitutions).unwrap(); + if (index instanceof IntConstant) { + return receiver.getElementByIndex(Number(index.value)); + } + } else if (receiver instanceof EvaluatedMap) { + const key = this.cachedDoEvaluate(node.index, substitutions).unwrap(); + return receiver.getLastValueForKey(key); + } + + return UnknownEvaluatedNode; + } + + private evaluateMemberAccess(node: SdsMemberAccess, _substitutions: ParameterSubstitutions): EvaluatedNode { + const member = node.member?.target?.ref; + if (isSdsEnumVariant(member)) { + return new EvaluatedEnumVariant(member, undefined); + } + + // return when (val simpleReceiver = receiver.evaluate(substitutions)) { + // SdsConstantNull -> when { + // isNullSafe -> SdsConstantNull + // else -> undefined + // } + // is SdsIntermediateRecord -> simpleReceiver.getSubstitutionByReferenceOrNull(member) + // else -> undefined + // } + return UnknownEvaluatedNode; + } + + private evaluateReference(_node: SdsReference, _substitutions: ParameterSubstitutions): EvaluatedNode { + // const target = node.target.ref; + + // is SdsPlaceholder -> declaration.evaluateAssignee(substitutions) + // is SdsParameter -> declaration.evaluateParameter(substitutions) + // is SdsStep -> declaration.evaluateStep() + // else -> undefined + // } + return UnknownEvaluatedNode; + } + + // private fun SdsAbstractAssignee.evaluateAssignee(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? { + // val simpleFullAssignedExpression = closestAncestorOrNull() + // ?.expression + // ?.evaluate(substitutions) + // ?: return undefined + // + // return when (simpleFullAssignedExpression) { + // is SdsIntermediateRecord -> simpleFullAssignedExpression.getSubstitutionByIndexOrNull(indexOrNull()) + // else -> when { + // indexOrNull() == 0 -> simpleFullAssignedExpression + // else -> undefined + // } + // } + // } + // + // private fun SdsParameter.evaluateParameter(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? { + // return when { + // this in substitutions -> substitutions[this] + // isOptional() -> defaultValue?.evaluate(substitutions) + // else -> undefined + // } + // } + // + // private fun SdsStep.evaluateStep(): SdsIntermediateStep? { + // return when { + // callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateStep( + // parameters = parametersOrEmpty(), + // results = resultsOrEmpty() + // ) + // else -> undefined + // } + // } +} + +const NO_SUBSTITUTIONS: ParameterSubstitutions = new Map(); +const zeroes = [new IntConstant(BigInt(0)), new FloatConstant(0.0), new FloatConstant(-0.0)]; diff --git a/src/language/partialEvaluation/toConstantExpression.ts b/src/language/partialEvaluation/toConstantExpression.ts deleted file mode 100644 index fbb021d6d..000000000 --- a/src/language/partialEvaluation/toConstantExpression.ts +++ /dev/null @@ -1,469 +0,0 @@ -import { - ConstantBoolean, - ConstantExpression, - ConstantFloat, - ConstantInt, - ConstantList, - ConstantMap, - ConstantNull, - ConstantString, - ParameterSubstitutions, - SimplifiedExpression, - UnknownValue, -} from './model.js'; -import { AstNode } from 'langium'; -import { - isSdsArgument, - isSdsBlockLambda, - isSdsBoolean, - isSdsCall, - isSdsExpression, - isSdsExpressionLambda, - isSdsFloat, - isSdsIndexedAccess, - isSdsInfixOperation, - isSdsInt, - isSdsList, - isSdsMap, - isSdsMemberAccess, - isSdsNull, - isSdsParenthesizedExpression, - isSdsPrefixOperation, - isSdsReference, - isSdsString, - isSdsTemplateString, - isSdsTemplateStringEnd, - isSdsTemplateStringInner, - isSdsTemplateStringStart, - SdsBlockLambda, - SdsCall, - SdsExpressionLambda, - SdsIndexedAccess, - SdsInfixOperation, - SdsMemberAccess, - SdsPrefixOperation, - SdsReference, - SdsTemplateString, -} from '../generated/ast.js'; - -/* c8 ignore start */ -/** - * Tries to evaluate this expression. - */ -export const toConstantExpression = (node: AstNode | undefined): ConstantExpression => { - if (!node) { - return UnknownValue; - } - - return toConstantExpressionImpl(node, new Map()); -}; - -const toConstantExpressionImpl = (node: AstNode, substitutions: ParameterSubstitutions): ConstantExpression => { - const simplifiedExpression = simplify(node, substitutions)?.unwrap(); - if (simplifiedExpression instanceof ConstantExpression) { - return simplifiedExpression; - } else { - return UnknownValue; - } -}; - -const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SimplifiedExpression => { - // Only expressions have a value - if (!isSdsExpression(node)) { - return UnknownValue; - } - - // Base cases - if (isSdsBoolean(node)) { - return new ConstantBoolean(node.value); - } else if (isSdsFloat(node)) { - return new ConstantFloat(node.value); - } else if (isSdsInt(node)) { - return new ConstantInt(BigInt(node.value)); - } else if (isSdsNull(node)) { - return ConstantNull; - } else if (isSdsString(node)) { - return new ConstantString(node.value); - } else if (isSdsTemplateStringStart(node)) { - return new ConstantString(node.value); - } else if (isSdsTemplateStringInner(node)) { - return new ConstantString(node.value); - } else if (isSdsTemplateStringEnd(node)) { - return new ConstantString(node.value); - } else if (isSdsBlockLambda(node)) { - return simplifyBlockLambda(node, substitutions); - } else if (isSdsExpressionLambda(node)) { - return simplifyExpressionLambda(node, substitutions); - } - - // Simple recursive cases - else if (isSdsArgument(node)) { - return simplify(node.value, substitutions); - } else if (isSdsInfixOperation(node)) { - return simplifyInfixOperation(node, substitutions); - } else if (isSdsList(node)) { - return new ConstantList([]); - } else if (isSdsMap(node)) { - return new ConstantMap([]); - } else if (isSdsParenthesizedExpression(node)) { - return simplify(node.expression, substitutions); - } else if (isSdsPrefixOperation(node)) { - return simplifyPrefixOperation(node, substitutions); - } else if (isSdsTemplateString(node)) { - return simplifyTemplateString(node, substitutions); - } - - // Complex recursive cases - else if (isSdsCall(node)) { - return simplifyCall(node, substitutions); - } else if (isSdsIndexedAccess(node)) { - return simplifyIndexedAccess(node, substitutions); - } else if (isSdsMemberAccess(node)) { - return simplifyMemberAccess(node, substitutions); - } else if (isSdsReference(node)) { - return simplifyReference(node, substitutions); - } - - // Raise if case is missing (should not happen) - /* c8 ignore next */ - throw new Error(`Missing case to handle ${node.$type}.`); -}; - -const simplifyBlockLambda = (_node: SdsBlockLambda, _substitutions: ParameterSubstitutions): SimplifiedExpression => { - // return when { - // callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateBlockLambda( - // parameters = parametersOrEmpty(), - // results = blockLambdaResultsOrEmpty(), - // substitutionsOnCreation = substitutions - // ) - // else -> undefined - // } - return UnknownValue; -}; - -const simplifyExpressionLambda = ( - _node: SdsExpressionLambda, - _substitutions: ParameterSubstitutions, -): SimplifiedExpression => { - // return when { - // callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateExpressionLambda( - // parameters = parametersOrEmpty(), - // result = result, - // substitutionsOnCreation = substitutions - // ) - // else -> undefined - // } - return UnknownValue; -}; - -const simplifyInfixOperation = ( - node: SdsInfixOperation, - substitutions: ParameterSubstitutions, -): SimplifiedExpression => { - // By design none of the operators are short-circuited - const constantLeft = toConstantExpressionImpl(node.leftOperand, substitutions); - if (constantLeft === UnknownValue) { - return UnknownValue; - } - - const constantRight = toConstantExpressionImpl(node.rightOperand, substitutions); - if (constantRight === UnknownValue) { - return UnknownValue; - } - - // return when (operator()) { - // Or -> simplifyLogicalOp(constantLeft, Boolean::or, constantRight) - // And -> simplifyLogicalOp(constantLeft, Boolean::and, constantRight) - // Equals -> SdsConstantBoolean(constantLeft == constantRight) - // NotEquals -> SdsConstantBoolean(constantLeft != constantRight) - // IdenticalTo -> SdsConstantBoolean(constantLeft == constantRight) - // NotIdenticalTo -> SdsConstantBoolean(constantLeft != constantRight) - // LessThan -> simplifyComparisonOp( - // constantLeft, - // { a, b -> a < b }, - // { a, b -> a < b }, - // constantRight - // ) - // LessThanOrEquals -> simplifyComparisonOp( - // constantLeft, - // { a, b -> a <= b }, - // { a, b -> a <= b }, - // constantRight - // ) - // GreaterThanOrEquals -> simplifyComparisonOp( - // constantLeft, - // { a, b -> a >= b }, - // { a, b -> a >= b }, - // constantRight - // ) - // GreaterThan -> simplifyComparisonOp( - // constantLeft, - // { a, b -> a > b }, - // { a, b -> a > b }, - // constantRight - // ) - // Plus -> simplifyArithmeticOp( - // constantLeft, - // { a, b -> a + b }, - // { a, b -> a + b }, - // constantRight - // ) - // InfixMinus -> simplifyArithmeticOp( - // constantLeft, - // { a, b -> a - b }, - // { a, b -> a - b }, - // constantRight - // ) - // Times -> simplifyArithmeticOp( - // constantLeft, - // { a, b -> a * b }, - // { a, b -> a * b }, - // constantRight - // ) - // By -> { - // if (constantRight == SdsConstantFloat(0.0) || constantRight == SdsConstantInt(0)) { - // return undefined - // } - // - // simplifyArithmeticOp( - // constantLeft, - // { a, b -> a / b }, - // { a, b -> a / b }, - // constantRight - // ) - // } - // Elvis -> when (constantLeft) { - // SdsConstantNull -> constantRight - // else -> constantLeft - // } - // } - return UnknownValue; -}; - -// private fun simplifyLogicalOp( -// leftOperand: SdsConstantExpression, -// operation: (Boolean, Boolean) -> Boolean, -// rightOperand: SdsConstantExpression, -// ): SdsConstantExpression? { -// -// return when { -// leftOperand is SdsConstantBoolean && rightOperand is SdsConstantBoolean -> { -// SdsConstantBoolean(operation(leftOperand.value, rightOperand.value)) -// } -// else -> undefined -// } -// } -// -// private fun simplifyComparisonOp( -// leftOperand: SdsConstantExpression, -// doubleOperation: (Double, Double) -> Boolean, -// intOperation: (Int, Int) -> Boolean, -// rightOperand: SdsConstantExpression, -// ): SdsConstantExpression? { -// -// return when { -// leftOperand is SdsConstantInt && rightOperand is SdsConstantInt -> { -// SdsConstantBoolean(intOperation(leftOperand.value, rightOperand.value)) -// } -// leftOperand is SdsConstantNumber && rightOperand is SdsConstantNumber -> { -// SdsConstantBoolean(doubleOperation(leftOperand.value.toDouble(), rightOperand.value.toDouble())) -// } -// else -> undefined -// } -// } -// -// private fun simplifyArithmeticOp( -// leftOperand: SdsConstantExpression, -// doubleOperation: (Double, Double) -> Double, -// intOperation: (Int, Int) -> Int, -// rightOperand: SdsConstantExpression, -// ): SdsConstantExpression? { -// -// return when { -// leftOperand is SdsConstantInt && rightOperand is SdsConstantInt -> { -// SdsConstantInt(intOperation(leftOperand.value, rightOperand.value)) -// } -// leftOperand is SdsConstantNumber && rightOperand is SdsConstantNumber -> { -// SdsConstantFloat(doubleOperation(leftOperand.value.toDouble(), rightOperand.value.toDouble())) -// } -// else -> undefined -// } -// } - -const simplifyPrefixOperation = ( - node: SdsPrefixOperation, - substitutions: ParameterSubstitutions, -): SimplifiedExpression => { - const constantOperand = toConstantExpressionImpl(node.operand, substitutions); - if (constantOperand === UnknownValue) { - return UnknownValue; - } - - if (node.operator === 'not') { - if (constantOperand instanceof ConstantBoolean) { - return new ConstantBoolean(!constantOperand.value); - } - } else if (node.operator === '-') { - if (constantOperand instanceof ConstantFloat) { - return new ConstantFloat(-constantOperand.value); - } else if (constantOperand instanceof ConstantInt) { - return new ConstantInt(-constantOperand.value); - } - } - - return UnknownValue; -}; - -const simplifyTemplateString = ( - node: SdsTemplateString, - substitutions: ParameterSubstitutions, -): SimplifiedExpression => { - const constantExpressions = node.expressions.map((it) => toConstantExpressionImpl(it, substitutions)); - if (constantExpressions.some((it) => it === UnknownValue)) { - return UnknownValue; - } - - return new ConstantString(constantExpressions.map((it) => it!.toInterpolationString()).join('')); -}; - -const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): SimplifiedExpression => { - // val simpleReceiver = simplifyReceiver(substitutions) ?: return undefined - // val newSubstitutions = buildNewSubstitutions(simpleReceiver, substitutions) - // - // return when (simpleReceiver) { - // is SdsIntermediateBlockLambda -> { - // SdsIntermediateRecord( - // simpleReceiver.results.map { - // it to it.simplifyAssignee(newSubstitutions) - // } - // ) - // } - // is SdsIntermediateExpressionLambda -> simpleReceiver.result.simplify(newSubstitutions) - // is SdsIntermediateStep -> { - // SdsIntermediateRecord( - // simpleReceiver.results.map { - // it to it.uniqueYieldOrNull()?.simplifyAssignee(newSubstitutions) - // } - // ) - // } - // } - return UnknownValue; -}; - -// private fun SdsCall.simplifyReceiver(substitutions: ParameterSubstitutions): SdsIntermediateCallable? { -// return when (val simpleReceiver = receiver.simplify(substitutions)) { -// is SdsIntermediateRecord -> simpleReceiver.unwrap() as? SdsIntermediateCallable -// is SdsIntermediateCallable -> simpleReceiver -// else -> return undefined -// } -// } -// -// private fun SdsCall.buildNewSubstitutions( -// simpleReceiver: SdsIntermediateCallable, -// oldSubstitutions: ParameterSubstitutions -// ): ParameterSubstitutions { -// -// val substitutionsOnCreation = when (simpleReceiver) { -// is SdsIntermediateBlockLambda -> simpleReceiver.substitutionsOnCreation -// is SdsIntermediateExpressionLambda -> simpleReceiver.substitutionsOnCreation -// else -> emptyMap() -// } -// -// val substitutionsOnCall = argumentsOrEmpty() -// .groupBy { it.parameterOrNull() } -// .mapValues { (parameter, arguments) -> -// when { -// parameter == undefined -> undefined -// parameter.isVariadic -> SdsIntermediateVariadicArguments( -// arguments.map { it.simplify(oldSubstitutions) } -// ) -// else -> arguments.uniqueOrNull()?.simplify(oldSubstitutions) -// } -// } -// -// return buildMap { -// putAll(substitutionsOnCreation) -// substitutionsOnCall.entries.forEach { (parameter, argument) -> -// if (parameter != undefined) { -// put(parameter, argument) -// } -// } -// } -// } - -const simplifyIndexedAccess = ( - _node: SdsIndexedAccess, - _substitutions: ParameterSubstitutions, -): SimplifiedExpression => { - // val simpleReceiver = receiver.simplify(substitutions) as? SdsIntermediateVariadicArguments ?: return undefined - // val simpleIndex = index.simplify(substitutions) as? SdsConstantInt ?: return undefined - // - // return simpleReceiver.getArgumentByIndexOrNull(simpleIndex.value) - // } - return UnknownValue; -}; - -const simplifyMemberAccess = (_node: SdsMemberAccess, _substitutions: ParameterSubstitutions): SimplifiedExpression => { - // private fun SdsMemberAccess.simplifyMemberAccess(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? { - // if (member.declaration is SdsEnumVariant) { - // return member.simplifyReference(substitutions) - // } - // - // return when (val simpleReceiver = receiver.simplify(substitutions)) { - // SdsConstantNull -> when { - // isNullSafe -> SdsConstantNull - // else -> undefined - // } - // is SdsIntermediateRecord -> simpleReceiver.getSubstitutionByReferenceOrNull(member) - // else -> undefined - // } - return UnknownValue; -}; - -const simplifyReference = (_node: SdsReference, _substitutions: ParameterSubstitutions): SimplifiedExpression => { - // return when (val declaration = this.declaration) { - // is SdsEnumVariant -> when { - // declaration.parametersOrEmpty().isEmpty() -> SdsConstantEnumVariant(declaration) - // else -> undefined - // } - // is SdsPlaceholder -> declaration.simplifyAssignee(substitutions) - // is SdsParameter -> declaration.simplifyParameter(substitutions) - // is SdsStep -> declaration.simplifyStep() - // else -> undefined - // } - return UnknownValue; -}; - -// private fun SdsAbstractAssignee.simplifyAssignee(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? { -// val simpleFullAssignedExpression = closestAncestorOrNull() -// ?.expression -// ?.simplify(substitutions) -// ?: return undefined -// -// return when (simpleFullAssignedExpression) { -// is SdsIntermediateRecord -> simpleFullAssignedExpression.getSubstitutionByIndexOrNull(indexOrNull()) -// else -> when { -// indexOrNull() == 0 -> simpleFullAssignedExpression -// else -> undefined -// } -// } -// } -// -// private fun SdsParameter.simplifyParameter(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? { -// return when { -// this in substitutions -> substitutions[this] -// isOptional() -> defaultValue?.simplify(substitutions) -// else -> undefined -// } -// } -// -// private fun SdsStep.simplifyStep(): SdsIntermediateStep? { -// return when { -// callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateStep( -// parameters = parametersOrEmpty(), -// results = resultsOrEmpty() -// ) -// else -> undefined -// } -// } -/* c8 ignore stop */ diff --git a/src/language/safe-ds-module.ts b/src/language/safe-ds-module.ts index b4ffc13a3..524fd4473 100644 --- a/src/language/safe-ds-module.ts +++ b/src/language/safe-ds-module.ts @@ -22,6 +22,7 @@ import { SafeDsPackageManager } from './workspace/safe-ds-package-manager.js'; import { SafeDsNodeMapper } from './helpers/safe-ds-node-mapper.js'; import { SafeDsAnnotations } from './builtins/safe-ds-annotations.js'; import { SafeDsClassHierarchy } from './typing/safe-ds-class-hierarchy.js'; +import { SafeDsPartialEvaluator } from './partialEvaluation/safe-ds-partial-evaluator.js'; /** * Declaration of custom services - add your own service classes here. @@ -31,6 +32,9 @@ export type SafeDsAddedServices = { Annotations: SafeDsAnnotations; Classes: SafeDsClasses; }; + evaluation: { + PartialEvaluator: SafeDsPartialEvaluator; + }; helpers: { NodeMapper: SafeDsNodeMapper; }; @@ -59,6 +63,9 @@ export const SafeDsModule: Module new SafeDsAnnotations(services), Classes: (services) => new SafeDsClasses(services), }, + evaluation: { + PartialEvaluator: (services) => new SafeDsPartialEvaluator(services), + }, helpers: { NodeMapper: (services) => new SafeDsNodeMapper(services), }, diff --git a/src/language/typing/safe-ds-type-computer.ts b/src/language/typing/safe-ds-type-computer.ts index bc80d7fb4..f52b9fc33 100644 --- a/src/language/typing/safe-ds-type-computer.ts +++ b/src/language/typing/safe-ds-type-computer.ts @@ -381,6 +381,7 @@ export class SafeDsTypeComputer { private computeTypeOfIndexedAccess(node: SdsIndexedAccess): Type { const receiverType = this.computeType(node.receiver); if (receiverType.equals(this.List) || receiverType.equals(this.Map)) { + // TODO: access type arguments return this.AnyOrNull(); } else { return UnknownType; diff --git a/src/language/validation/other/declarations/annotationCalls.ts b/src/language/validation/other/declarations/annotationCalls.ts index 57b40d127..103a6e755 100644 --- a/src/language/validation/other/declarations/annotationCalls.ts +++ b/src/language/validation/other/declarations/annotationCalls.ts @@ -16,26 +16,29 @@ import { resultsOrEmpty, } from '../../../helpers/nodeProperties.js'; import { isEmpty } from 'radash'; -import { toConstantExpression } from '../../../partialEvaluation/toConstantExpression.js'; -import { UnknownValue } from '../../../partialEvaluation/model.js'; +import { SafeDsServices } from '../../../safe-ds-module.js'; export const CODE_ANNOTATION_CALL_CONSTANT_ARGUMENT = 'annotation-call/constant-argument'; export const CODE_ANNOTATION_CALL_MISSING_ARGUMENT_LIST = 'annotation-call/missing-argument-list'; export const CODE_ANNOTATION_CALL_TARGET_PARAMETER = 'annotation-call/target-parameter'; export const CODE_ANNOTATION_CALL_TARGET_RESULT = 'annotation-call/target-result'; -export const annotationCallArgumentsMustBeConstant = (node: SdsAnnotationCall, accept: ValidationAcceptor) => { - for (const argument of argumentsOrEmpty(node)) { - const constantValue = toConstantExpression(argument.value); +export const annotationCallArgumentsMustBeConstant = (services: SafeDsServices) => { + const partialEvaluator = services.evaluation.PartialEvaluator; - if (constantValue === UnknownValue) { - accept('error', 'Arguments of annotation calls must be constant.', { - node: argument, - property: 'value', - code: CODE_ANNOTATION_CALL_CONSTANT_ARGUMENT, - }); + return (node: SdsAnnotationCall, accept: ValidationAcceptor) => { + for (const argument of argumentsOrEmpty(node)) { + const evaluatedArgumentValue = partialEvaluator.evaluate(argument.value); + + if (!evaluatedArgumentValue.isFullyEvaluated) { + accept('error', 'Arguments of annotation calls must be constant.', { + node: argument, + property: 'value', + code: CODE_ANNOTATION_CALL_CONSTANT_ARGUMENT, + }); + } } - } + }; }; export const annotationCallMustNotLackArgumentList = (node: SdsAnnotationCall, accept: ValidationAcceptor) => { diff --git a/src/language/validation/other/declarations/parameters.ts b/src/language/validation/other/declarations/parameters.ts index 624168a2e..583e40db5 100644 --- a/src/language/validation/other/declarations/parameters.ts +++ b/src/language/validation/other/declarations/parameters.ts @@ -1,23 +1,26 @@ import { isSdsAnnotation, isSdsCallable, SdsParameter } from '../../../generated/ast.js'; import { getContainerOfType, ValidationAcceptor } from 'langium'; import { isConstantParameter } from '../../../helpers/nodeProperties.js'; -import { toConstantExpression } from '../../../partialEvaluation/toConstantExpression.js'; -import { UnknownValue } from '../../../partialEvaluation/model.js'; +import { SafeDsServices } from '../../../safe-ds-module.js'; export const CODE_PARAMETER_CONSTANT_DEFAULT_VALUE = 'parameter/constant-default-value'; -export const constantParameterMustHaveConstantDefaultValue = (node: SdsParameter, accept: ValidationAcceptor) => { - if (!isConstantParameter(node) || !node.defaultValue) return; +export const constantParameterMustHaveConstantDefaultValue = (services: SafeDsServices) => { + const partialEvaluator = services.evaluation.PartialEvaluator; - const defaultValue = toConstantExpression(node.defaultValue); - if (defaultValue === UnknownValue) { - const containingCallable = getContainerOfType(node, isSdsCallable); - const kind = isSdsAnnotation(containingCallable) ? 'annotation' : 'constant'; + return (node: SdsParameter, accept: ValidationAcceptor) => { + if (!isConstantParameter(node) || !node.defaultValue) return; - accept('error', `Default values of ${kind} parameters must be constant.`, { - node, - property: 'defaultValue', - code: CODE_PARAMETER_CONSTANT_DEFAULT_VALUE, - }); - } + const evaluatedDefaultValue = partialEvaluator.evaluate(node.defaultValue); + if (!evaluatedDefaultValue.isFullyEvaluated) { + const containingCallable = getContainerOfType(node, isSdsCallable); + const kind = isSdsAnnotation(containingCallable) ? 'annotation' : 'constant'; + + accept('error', `Default values of ${kind} parameters must be constant.`, { + node, + property: 'defaultValue', + code: CODE_PARAMETER_CONSTANT_DEFAULT_VALUE, + }); + } + }; }; diff --git a/src/language/validation/other/expressions/calls.ts b/src/language/validation/other/expressions/calls.ts index 3f63c2d70..0a3e248bf 100644 --- a/src/language/validation/other/expressions/calls.ts +++ b/src/language/validation/other/expressions/calls.ts @@ -1,22 +1,21 @@ import { SdsCall } from '../../../generated/ast.js'; import { ValidationAcceptor } from 'langium'; import { argumentsOrEmpty, isConstantParameter } from '../../../helpers/nodeProperties.js'; -import { toConstantExpression } from '../../../partialEvaluation/toConstantExpression.js'; import { SafeDsServices } from '../../../safe-ds-module.js'; -import { UnknownValue } from '../../../partialEvaluation/model.js'; export const CODE_CALL_CONSTANT_ARGUMENT = 'call/constant-argument'; export const callArgumentsMustBeConstantIfParameterIsConstant = (services: SafeDsServices) => { const nodeMapper = services.helpers.NodeMapper; + const partialEvaluator = services.evaluation.PartialEvaluator; return (node: SdsCall, accept: ValidationAcceptor) => { for (const argument of argumentsOrEmpty(node)) { const parameter = nodeMapper.argumentToParameterOrUndefined(argument); if (!isConstantParameter(parameter)) continue; - const value = toConstantExpression(argument.value); - if (value === UnknownValue) { + const evaluatedArgumentValue = partialEvaluator.evaluate(argument.value); + if (!evaluatedArgumentValue.isFullyEvaluated) { accept('error', 'Arguments assigned to constant parameters must be constant.', { node: argument, property: 'value', diff --git a/src/language/validation/other/expressions/infixOperations.ts b/src/language/validation/other/expressions/infixOperations.ts index 58f703bbd..a711aa77a 100644 --- a/src/language/validation/other/expressions/infixOperations.ts +++ b/src/language/validation/other/expressions/infixOperations.ts @@ -1,17 +1,18 @@ import { SdsInfixOperation } from '../../../generated/ast.js'; import { ValidationAcceptor } from 'langium'; import { SafeDsServices } from '../../../safe-ds-module.js'; -import { toConstantExpression } from '../../../partialEvaluation/toConstantExpression.js'; -import { ConstantFloat, ConstantInt } from '../../../partialEvaluation/model.js'; +import { FloatConstant, IntConstant, NumberConstant } from '../../../partialEvaluation/model.js'; import { UnknownType } from '../../../typing/model.js'; export const CODE_INFIX_OPERATION_DIVISION_BY_ZERO = 'infix-operation/division-by-zero'; export const divisionDivisorMustNotBeZero = (services: SafeDsServices) => { + const partialEvaluator = services.evaluation.PartialEvaluator; const typeComputer = services.types.TypeComputer; - const zeroInt = new ConstantInt(BigInt(0)); - const zeroFloat = new ConstantFloat(0.0); - const minusZeroFloat = new ConstantFloat(-0.0); + + const zeroInt = new IntConstant(BigInt(0)); + const zeroFloat = new FloatConstant(0.0); + const minusZeroFloat = new FloatConstant(-0.0); return (node: SdsInfixOperation, accept: ValidationAcceptor): void => { if (node.operator !== '/') { @@ -26,9 +27,9 @@ export const divisionDivisorMustNotBeZero = (services: SafeDsServices) => { return; } - const divisorValue = toConstantExpression(node.rightOperand); + const divisorValue = partialEvaluator.evaluate(node.rightOperand); if ( - divisorValue && + divisorValue instanceof NumberConstant && (divisorValue.equals(zeroInt) || divisorValue.equals(zeroFloat) || divisorValue.equals(minusZeroFloat)) ) { accept('error', 'Division by zero.', { diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 1f273da3f..1ebe702b5 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -145,7 +145,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { annotationCallAnnotationShouldNotBeDeprecated(services), annotationCallAnnotationShouldNotBeExperimental(services), annotationCallArgumentListShouldBeNeeded, - annotationCallArgumentsMustBeConstant, + annotationCallArgumentsMustBeConstant(services), annotationCallMustNotLackArgumentList, ], SdsArgument: [ @@ -219,7 +219,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments, ], SdsParameter: [ - constantParameterMustHaveConstantDefaultValue, + constantParameterMustHaveConstantDefaultValue(services), parameterMustHaveTypeHint, requiredParameterMustNotBeDeprecated(services), requiredParameterMustNotBeExpert(services), diff --git a/src/language/validation/style.ts b/src/language/validation/style.ts index a8c3fa8fd..6fe57595a 100644 --- a/src/language/validation/style.ts +++ b/src/language/validation/style.ts @@ -23,8 +23,7 @@ import { isEmpty } from 'radash'; import { isRequiredParameter, parametersOrEmpty, typeParametersOrEmpty } from '../helpers/nodeProperties.js'; import { SafeDsServices } from '../safe-ds-module.js'; import { UnknownType } from '../typing/model.js'; -import { toConstantExpression } from '../partialEvaluation/toConstantExpression.js'; -import { ConstantNull } from '../partialEvaluation/model.js'; +import { NullConstant } from '../partialEvaluation/model.js'; export const CODE_STYLE_UNNECESSARY_ASSIGNMENT = 'style/unnecessary-assignment'; export const CODE_STYLE_UNNECESSARY_ARGUMENT_LIST = 'style/unnecessary-argument-list'; @@ -160,6 +159,7 @@ export const constraintListShouldNotBeEmpty = (node: SdsConstraintList, accept: // ----------------------------------------------------------------------------- export const elvisOperatorShouldBeNeeded = (services: SafeDsServices) => { + const partialEvaluator = services.evaluation.PartialEvaluator; const typeComputer = services.types.TypeComputer; return (node: SdsInfixOperation, accept: ValidationAcceptor): void => { @@ -177,9 +177,9 @@ export const elvisOperatorShouldBeNeeded = (services: SafeDsServices) => { ); } - const leftValue = toConstantExpression(node.leftOperand); - const rightValue = toConstantExpression(node.rightOperand); - if (leftValue === ConstantNull && rightValue === ConstantNull) { + const leftValue = partialEvaluator.evaluate(node.leftOperand); + const rightValue = partialEvaluator.evaluate(node.rightOperand); + if (leftValue === NullConstant && rightValue === NullConstant) { accept( 'info', 'Both operands are always null, so the elvis operator is unnecessary (replace it with null).', @@ -188,7 +188,7 @@ export const elvisOperatorShouldBeNeeded = (services: SafeDsServices) => { code: CODE_STYLE_UNNECESSARY_ELVIS_OPERATOR, }, ); - } else if (leftValue === ConstantNull) { + } else if (leftValue === NullConstant) { accept( 'info', 'The left operand is always null, so the elvis operator is unnecessary (keep the right operand).', @@ -197,7 +197,7 @@ export const elvisOperatorShouldBeNeeded = (services: SafeDsServices) => { code: CODE_STYLE_UNNECESSARY_ELVIS_OPERATOR, }, ); - } else if (rightValue === ConstantNull) { + } else if (rightValue === NullConstant) { accept( 'info', 'The right operand is always null, so the elvis operator is unnecessary (keep the left operand).', diff --git a/syntaxes/safe-ds.tmLanguage.json b/syntaxes/safe-ds.tmLanguage.json index 40238aa67..5a5980c5c 100644 --- a/syntaxes/safe-ds.tmLanguage.json +++ b/syntaxes/safe-ds.tmLanguage.json @@ -35,10 +35,20 @@ "match": "\\b(as|from|import|literal|union|where|yield)\\b" }, { - "name": "entity.name.safe-ds", + "name": "meta.safe-ds", "begin": "\\`", "end": "\\`" }, + { + "name": "string.interpolated.safe-ds", + "begin": "\"|}}", + "end": "{{|\"", + "patterns": [ + { + "include": "#string-character-escape" + } + ] + }, { "name": "string.quoted.double.safe-ds", "begin": "\"", diff --git a/tests/language/partialEvaluation/creator.ts b/tests/language/partialEvaluation/creator.ts index 83c28a723..5eed55630 100644 --- a/tests/language/partialEvaluation/creator.ts +++ b/tests/language/partialEvaluation/creator.ts @@ -23,7 +23,6 @@ export const createPartialEvaluationTests = (): Promise const createPartialEvaluationTest = async (parentDirectory: URI, uris: URI[]): Promise => { const groupIdToLocations: Map = new Map(); const serializationAssertions: SerializationAssertion[] = []; - const undefinedAssertions: UndefinedAssertion[] = []; for (const uri of uris) { const code = fs.readFileSync(uri.fsPath).toString(); @@ -42,8 +41,8 @@ const createPartialEvaluationTest = async (parentDirectory: URI, uris: URI[]): P } for (const check of checksResult.value) { - // Expected unresolved reference - const equivalenceClassMatch = /constant equivalence_class (?.*)/gu.exec(check.comment); + // Partially evaluating a set of nodes should yield the same result + const equivalenceClassMatch = /equivalence_class (?.*)/gu.exec(check.comment); if (equivalenceClassMatch) { const id = equivalenceClassMatch.groups!.id; const priorLocationsInEquivalenceClass = groupIdToLocations.get(id) ?? []; @@ -52,8 +51,8 @@ const createPartialEvaluationTest = async (parentDirectory: URI, uris: URI[]): P continue; } - // Expected that reference is resolved and points to the target id - const serializationMatch = /constant serialization (?.*)/gu.exec(check.comment); + // Partially evaluating a node and serializing the result should yield the expected value. + const serializationMatch = /serialization (?.*)/gu.exec(check.comment); if (serializationMatch) { const expectedValue = serializationMatch.groups!.expectedValue; serializationAssertions.push({ @@ -63,15 +62,6 @@ const createPartialEvaluationTest = async (parentDirectory: URI, uris: URI[]): P continue; } - // Expected that reference is resolved and points to the target id - const undefinedMatch = /not constant/gu.exec(check.comment); - if (undefinedMatch) { - undefinedAssertions.push({ - location: check.location!, - }); - continue; - } - return invalidTest('FILE', new InvalidCommentError(check.comment, uri)); } } @@ -89,7 +79,6 @@ const createPartialEvaluationTest = async (parentDirectory: URI, uris: URI[]): P uris, equivalenceClassAssertions: [...groupIdToLocations.values()].map((locations) => ({ locations })), serializationAssertions, - undefinedAssertions, }; }; @@ -107,7 +96,6 @@ const invalidTest = (level: 'FILE' | 'SUITE', error: TestDescriptionError): Part uris: [], equivalenceClassAssertions: [], serializationAssertions: [], - undefinedAssertions: [], error, }; }; @@ -122,56 +110,41 @@ interface PartialEvaluationTest extends TestDescription { uris: URI[]; /** - * All nodes in an equivalence class should evaluate to the same constant expression. + * Partially evaluating nodes in the same equivalence class should yield the same result. */ equivalenceClassAssertions: EquivalenceClassAssertion[]; /** - * The serialized constant expression of a node should match the expected value. + * Partially evaluating a node and serializing the result should yield the expected value. */ serializationAssertions: SerializationAssertion[]; - - /** - * The node should not evaluate to a constant expression. - */ - undefinedAssertions: UndefinedAssertion[]; } /** - * A set of nodes should all evaluate to the same constant expression. + * Partially evaluating a set of nodes should yield the same result. */ interface EquivalenceClassAssertion { /** - * The locations of the nodes that should all evaluate to the same constant expression. + * The locations of the nodes to partially evaluate. */ locations: Location[]; } /** - * The serialized constant expression of a node should match the expected value. + * Partially evaluating a node and serializing the result should yield the expected value. */ interface SerializationAssertion { /** - * The location of the node whose serialized constant expression should be checked. + * The location of the node to partially evaluate. */ location: Location; /** - * The expected serialized constant expression of the node. + * The expected serialized evaluation of the node. */ expectedValue: string; } -/** - * The node should not evaluate to a constant expression. - */ -interface UndefinedAssertion { - /** - * The location of the node to check. - */ - location: Location; -} - /** * A test comment did not match the expected format. */ diff --git a/tests/language/partialEvaluation/testPartialEvaluation.test.ts b/tests/language/partialEvaluation/testPartialEvaluation.test.ts index 1a705ef60..533772466 100644 --- a/tests/language/partialEvaluation/testPartialEvaluation.test.ts +++ b/tests/language/partialEvaluation/testPartialEvaluation.test.ts @@ -5,12 +5,10 @@ import { clearDocuments } from 'langium/test'; import { AssertionError } from 'assert'; import { locationToString } from '../../helpers/location.js'; import { createPartialEvaluationTests } from './creator.js'; -import { toConstantExpression } from '../../../src/language/partialEvaluation/toConstantExpression.js'; -import { Location } from 'vscode-languageserver'; import { getNodeByLocation } from '../../helpers/nodeFinder.js'; -import { UnknownValue } from '../../../src/language/partialEvaluation/model.js'; const services = createSafeDsServices(NodeFileSystem).SafeDs; +const partialEvaluator = services.evaluation.PartialEvaluator; describe('partial evaluation', async () => { afterEach(async () => { @@ -27,26 +25,20 @@ describe('partial evaluation', async () => { const documents = test.uris.map((uri) => services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri)); await services.shared.workspace.DocumentBuilder.build(documents); - // Ensure all nodes in the equivalence class get evaluated to the same constant expression + // Ensure that partially evaluating nodes in the same equivalence class yields the same result for (const equivalenceClassAssertion of test.equivalenceClassAssertions) { if (equivalenceClassAssertion.locations.length > 1) { const firstLocation = equivalenceClassAssertion.locations[0]; const firstNode = getNodeByLocation(services, firstLocation); - const firstValue = toConstantExpression(firstNode); - if (!firstValue) { - return reportUndefinedValue(firstLocation); - } + const firstValue = partialEvaluator.evaluate(firstNode); for (const currentLocation of equivalenceClassAssertion.locations.slice(1)) { const currentNode = getNodeByLocation(services, currentLocation); - const currentValue = toConstantExpression(currentNode); - if (!currentValue) { - return reportUndefinedValue(currentLocation); - } + const currentValue = partialEvaluator.evaluate(currentNode); if (!currentValue.equals(firstValue)) { throw new AssertionError({ - message: `Two nodes in the same equivalence class evaluate to different constant expressions.\n Current location: ${locationToString( + message: `Two nodes in the same equivalence class are simplified differently.\n Current location: ${locationToString( currentLocation, )}\n First location: ${locationToString(firstLocation)}`, actual: currentValue.toString(), @@ -57,17 +49,14 @@ describe('partial evaluation', async () => { } } - // Ensure the serialized constant expression matches the expected one + // Ensure the serializing the result of partially evaluating a node yields the expected value for (const serializationAssertion of test.serializationAssertions) { const node = getNodeByLocation(services, serializationAssertion.location); - const actualValue = toConstantExpression(node); - if (!actualValue) { - return reportUndefinedValue(serializationAssertion.location); - } + const actualValue = partialEvaluator.evaluate(node); if (actualValue.toString() !== serializationAssertion.expectedValue) { throw new AssertionError({ - message: `A node has the wrong serialized constant expression.\n Location: ${locationToString( + message: `A node has the wrong serialized simplification.\n Location: ${locationToString( serializationAssertion.location, )}`, actual: actualValue.toString(), @@ -75,26 +64,5 @@ describe('partial evaluation', async () => { }); } } - - // Ensure the node does not evaluate to a constant expression - for (const undefinedAssertion of test.undefinedAssertions) { - const node = getNodeByLocation(services, undefinedAssertion.location); - const actualValue = toConstantExpression(node); - if (actualValue !== UnknownValue) { - throw new AssertionError({ - message: `A node evaluates to a constant expression, but it should not.\n Location: ${locationToString( - undefinedAssertion.location, - )}`, - actual: actualValue.toString(), - expected: '$unknown', - }); - } - } }); }); - -const reportUndefinedValue = (location: Location) => { - throw new AssertionError({ - message: `A node could not be evaluated to a constant expression.\n Location: ${locationToString(location)}`, - }); -}; diff --git a/tests/language/typing/creator.ts b/tests/language/typing/creator.ts index dd8cea537..1bc690c74 100644 --- a/tests/language/typing/creator.ts +++ b/tests/language/typing/creator.ts @@ -41,7 +41,7 @@ const createTypingTest = async (parentDirectory: URI, uris: URI[]): Promise.*)/gu.exec(check.comment); if (equivalenceClassMatch) { const id = equivalenceClassMatch.groups!.id; @@ -51,7 +51,7 @@ const createTypingTest = async (parentDirectory: URI, uris: URI[]): Promise.*)/gu.exec(check.comment); if (serializationMatch) { const expectedType = serializationMatch.groups!.expectedType; @@ -125,7 +125,7 @@ interface TypingTest extends TestDescription { */ interface EquivalenceClassAssertion { /** - * The locations of the nodes that should all get the same type. + * The locations of the nodes to compute the type of. */ locations: Location[]; } @@ -135,7 +135,7 @@ interface EquivalenceClassAssertion { */ interface SerializationAssertion { /** - * The location of the node whose serialized type should be checked. + * The location of the node to compute the type of. */ location: Location; diff --git a/tests/resources/partial evaluation/base cases/boolean literal/main.sdstest b/tests/resources/partial evaluation/base cases/boolean literal/main.sdstest deleted file mode 100644 index 913cb39b8..000000000 --- a/tests/resources/partial evaluation/base cases/boolean literal/main.sdstest +++ /dev/null @@ -1,9 +0,0 @@ -package tests.partialValidation.baseCases.booleanLiteral - -pipeline test { - // $TEST$ constant serialization false - »false«; - - // $TEST$ constant serialization true - »true«; -} diff --git a/tests/resources/partial evaluation/base cases/boolean literals/main.sdstest b/tests/resources/partial evaluation/base cases/boolean literals/main.sdstest new file mode 100644 index 000000000..c12e0ca70 --- /dev/null +++ b/tests/resources/partial evaluation/base cases/boolean literals/main.sdstest @@ -0,0 +1,9 @@ +package tests.partialValidation.baseCases.booleanLiterals + +pipeline test { + // $TEST$ serialization false + »false«; + + // $TEST$ serialization true + »true«; +} diff --git a/tests/resources/partial evaluation/base cases/float literal/main.sdstest b/tests/resources/partial evaluation/base cases/float literal/main.sdstest deleted file mode 100644 index 2910be3b8..000000000 --- a/tests/resources/partial evaluation/base cases/float literal/main.sdstest +++ /dev/null @@ -1,9 +0,0 @@ -package tests.partialValidation.baseCases.floatLiteral - -pipeline test { - // $TEST$ constant serialization 1.25 - »1.25«; - - // $TEST$ constant serialization 0.02 - »2e-2«; -} diff --git a/tests/resources/partial evaluation/base cases/float literals/main.sdstest b/tests/resources/partial evaluation/base cases/float literals/main.sdstest new file mode 100644 index 000000000..c5f6dab52 --- /dev/null +++ b/tests/resources/partial evaluation/base cases/float literals/main.sdstest @@ -0,0 +1,9 @@ +package tests.partialValidation.baseCases.floatLiterals + +pipeline test { + // $TEST$ serialization 1.25 + »1.25«; + + // $TEST$ serialization 0.02 + »2e-2«; +} diff --git a/tests/resources/partial evaluation/base cases/int literal/main.sdstest b/tests/resources/partial evaluation/base cases/int literal/main.sdstest deleted file mode 100644 index a3e4abd55..000000000 --- a/tests/resources/partial evaluation/base cases/int literal/main.sdstest +++ /dev/null @@ -1,6 +0,0 @@ -package tests.partialValidation.baseCases.intLiteral - -pipeline test { - // $TEST$ constant serialization 123 - »123«; -} diff --git a/tests/resources/partial evaluation/base cases/int literals/main.sdstest b/tests/resources/partial evaluation/base cases/int literals/main.sdstest new file mode 100644 index 000000000..6a9e1c387 --- /dev/null +++ b/tests/resources/partial evaluation/base cases/int literals/main.sdstest @@ -0,0 +1,6 @@ +package tests.partialValidation.baseCases.intLiterals + +pipeline test { + // $TEST$ serialization 123 + »123«; +} diff --git a/tests/resources/partial evaluation/base cases/null literal/main.sdstest b/tests/resources/partial evaluation/base cases/null literal/main.sdstest deleted file mode 100644 index 7b7e0a892..000000000 --- a/tests/resources/partial evaluation/base cases/null literal/main.sdstest +++ /dev/null @@ -1,6 +0,0 @@ -package tests.partialValidation.baseCases.nullLiteral - -pipeline test { - // $TEST$ constant serialization null - »null«; -} diff --git a/tests/resources/partial evaluation/base cases/null literals/main.sdstest b/tests/resources/partial evaluation/base cases/null literals/main.sdstest new file mode 100644 index 000000000..ce8914a9b --- /dev/null +++ b/tests/resources/partial evaluation/base cases/null literals/main.sdstest @@ -0,0 +1,6 @@ +package tests.partialValidation.baseCases.nullLiterals + +pipeline test { + // $TEST$ serialization null + »null«; +} diff --git a/tests/resources/partial evaluation/base cases/string literal (without interpolation)/main.sdstest b/tests/resources/partial evaluation/base cases/string literal (without interpolation)/main.sdstest deleted file mode 100644 index 4d835aa45..000000000 --- a/tests/resources/partial evaluation/base cases/string literal (without interpolation)/main.sdstest +++ /dev/null @@ -1,9 +0,0 @@ -package tests.partialValidation.baseCases.stringLiteralWithoutInterpolation - -pipeline test { - // $TEST$ constant serialization "test" - »"test"«; - - // $TEST$ constant serialization "test " - »"test\t"«; -} diff --git a/tests/resources/partial evaluation/base cases/string literals (without interpolation)/main.sdstest b/tests/resources/partial evaluation/base cases/string literals (without interpolation)/main.sdstest new file mode 100644 index 000000000..d01e24d4e --- /dev/null +++ b/tests/resources/partial evaluation/base cases/string literals (without interpolation)/main.sdstest @@ -0,0 +1,9 @@ +package tests.partialValidation.baseCases.stringLiteralsWithoutInterpolation + +pipeline test { + // $TEST$ serialization "test" + »"test"«; + + // $TEST$ serialization "test " + »"test\t"«; +} diff --git a/tests/resources/partial evaluation/recursive cases/arguments/main.sdstest b/tests/resources/partial evaluation/recursive cases/arguments/main.sdstest new file mode 100644 index 000000000..487939d98 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/arguments/main.sdstest @@ -0,0 +1,11 @@ +package tests.partialValidation.recursiveCases.arguments + +fun f(p: Any?) + +pipeline test { + // $TEST$ serialization 1 + f(»p = 1«); + + // $TEST$ serialization null + f(»p = null«); +} diff --git a/tests/resources/partial evaluation/recursive cases/calls/of enum variants/main.sdstest b/tests/resources/partial evaluation/recursive cases/calls/of enum variants/main.sdstest new file mode 100644 index 000000000..0ea9b94d4 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/calls/of enum variants/main.sdstest @@ -0,0 +1,37 @@ +package tests.partialValidation.recursiveCases.calls.ofEnumVariants + +enum MyEnum { + MyEnumVariantWithoutParameters + MyEnumVariantWithParameters(p: Int, q: Int = 3) +} + +fun f() -> r: Int + +pipeline test { + // $TEST$ serialization MyEnumVariantWithoutParameters() + »MyEnum.MyEnumVariantWithoutParameters()«; + + // $TEST$ serialization MyEnumVariantWithParameters($unknown, 3) + »MyEnum.MyEnumVariantWithParameters()«; + + // $TEST$ serialization MyEnumVariantWithParameters(1, 3) + »MyEnum.MyEnumVariantWithParameters(1)«; + + // $TEST$ serialization MyEnumVariantWithParameters(1, 2) + »MyEnum.MyEnumVariantWithParameters(1, 2)«; + + // $TEST$ serialization MyEnumVariantWithParameters(1, 2) + »MyEnum.MyEnumVariantWithParameters(q = 2, p = 1)«; + + // $TEST$ serialization MyEnumVariantWithParameters(1, $unknown) + »MyEnum.MyEnumVariantWithParameters(1, f())«; + + // $TEST$ serialization MyEnumVariantWithParameters(1, $unknown) + »MyEnum.MyEnumVariantWithParameters(1, f(), 3)«; + + // $TEST$ serialization MyEnumVariantWithParameters(1, $unknown) + »MyEnum.MyEnumVariantWithParameters(1, f(), r = 3)«; + + // $TEST$ serialization $unknown + »MyEnum.MyEnumVariantWithParameters(q = 2, p = 1)()«; +} diff --git a/tests/resources/partial evaluation/recursive cases/calls/unresolved/main.sdstest b/tests/resources/partial evaluation/recursive cases/calls/unresolved/main.sdstest new file mode 100644 index 000000000..96c4c643c --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/calls/unresolved/main.sdstest @@ -0,0 +1,13 @@ +package tests.partialValidation.recursiveCases.calls.unresolved + +enum MyEnum { + MyEnumVariant +} + +pipeline test { + // $TEST$ serialization $unknown + »unresolved.MyEnumVariant()«; + + // $TEST$ serialization $unknown + »MyEnum.unresolved()«; +} diff --git a/tests/resources/partial evaluation/recursive cases/indexed access/on lists/main.sdstest b/tests/resources/partial evaluation/recursive cases/indexed access/on lists/main.sdstest new file mode 100644 index 000000000..eb9d06591 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/indexed access/on lists/main.sdstest @@ -0,0 +1,24 @@ +package tests.partialValidation.recursiveCases.indexedAccess.onLists + +pipeline test { + // $TEST$ serialization 2 + »[1, 2, 3][1]«; + + // $TEST$ serialization 1 + »[1, 2, unresolved][0]«; + + // $TEST$ serialization $unknown + »[1, 2, unresolved][2]«; + + // $TEST$ serialization $unknown + »[][-1]«; + + // $TEST$ serialization $unknown + »[][1]«; + + // $TEST$ serialization $unknown + »[][""]«; + + // $TEST$ serialization $unknown + »[][unresolved]«; +} diff --git a/tests/resources/partial evaluation/recursive cases/indexed access/on maps/main.sdstest b/tests/resources/partial evaluation/recursive cases/indexed access/on maps/main.sdstest new file mode 100644 index 000000000..a7d9fc73f --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/indexed access/on maps/main.sdstest @@ -0,0 +1,21 @@ +package tests.partialValidation.recursiveCases.indexedAccess.onMaps + +pipeline test { + // $TEST$ serialization 1 + »{"1": 1}["1"]«; + + // $TEST$ serialization 2 + »{"1": 1, "1": 2}["1"]«; // In Python the last matching entry wins, so we need to mimic that behavior + + // $TEST$ serialization 2 + »{"1": 1, "2": 2, "3": unresolved}["2"]«; + + // $TEST$ serialization $unknown + »{"1": 1, "2": 2, "3": unresolved}["3"]«; + + // $TEST$ serialization $unknown + »{}[1]«; + + // $TEST$ serialization $unknown + »{}[unresolved]«; +} diff --git a/tests/resources/partial evaluation/recursive cases/indexed access/on other/main.sdstest b/tests/resources/partial evaluation/recursive cases/indexed access/on other/main.sdstest new file mode 100644 index 000000000..b813c00a7 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/indexed access/on other/main.sdstest @@ -0,0 +1,9 @@ +package tests.partialValidation.recursiveCases.indexedAccess.onOther + +pipeline test { + // $TEST$ serialization $unknown + »1[1]«; + + // $TEST$ serialization $unknown + »unresolved[1]«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/and/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/and/main.sdstest new file mode 100644 index 000000000..48353e16e --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/and/main.sdstest @@ -0,0 +1,27 @@ +package tests.partialValidation.recursiveCases.infixOperations.`and` + +pipeline test { + // $TEST$ serialization false + »false and false«; + + // $TEST$ serialization false + »false and true«; + + // $TEST$ serialization false + »true and false«; + + // $TEST$ serialization true + »true and true«; + + // $TEST$ serialization $unknown + »1 and true«; + + // $TEST$ serialization $unknown + »false and 0«; + + // $TEST$ serialization $unknown + »unresolved and false«; + + // $TEST$ serialization $unknown + »true and unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/divided by/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/divided by/main.sdstest new file mode 100644 index 000000000..757a3dbe5 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/divided by/main.sdstest @@ -0,0 +1,38 @@ +package tests.partialValidation.recursiveCases.infixOperations.dividedBy + +pipeline test { + // $TEST$ serialization 0.5 + »0.25 / 0.5«; + + // $TEST$ serialization 1.5 + »1.5 / 1«; + + // $TEST$ serialization 1.6 + »1 / 0.625«; + + // $TEST$ serialization 1 + »1 / 1«; + + + // $TEST$ serialization $unknown + »1 / 0«; + + // $TEST$ serialization $unknown + »1 / 0.0«; + + // $TEST$ serialization $unknown + »1 / -0.0«; + + + // $TEST$ serialization $unknown + »true / 1«; + + // $TEST$ serialization $unknown + »1 / true«; + + // $TEST$ serialization $unknown + »unresolved / 1«; + + // $TEST$ serialization $unknown + »1 / unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/elvis/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/elvis/main.sdstest new file mode 100644 index 000000000..021a76e83 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/elvis/main.sdstest @@ -0,0 +1,15 @@ +package tests.partialValidation.recursiveCases.infixOperations.elvis + +pipeline test { + // $TEST$ serialization true + »true ?: false«; + + // $TEST$ serialization false + »null ?: false«; + + // $TEST$ serialization $unknown + »unresolved ?: true«; + + // $TEST$ serialization $unknown + »true ?: unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/equals/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/equals/main.sdstest new file mode 100644 index 000000000..96986fcaa --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/equals/main.sdstest @@ -0,0 +1,15 @@ +package tests.partialValidation.recursiveCases.infixOperations.equals + +pipeline test { + // $TEST$ serialization true + »true == true«; + + // $TEST$ serialization false + »false == 1«; + + // $TEST$ serialization $unknown + »1 == unresolved«; + + // $TEST$ serialization $unknown + »unresolved == 1.25«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/greater than or equals/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/greater than or equals/main.sdstest new file mode 100644 index 000000000..d949d7375 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/greater than or equals/main.sdstest @@ -0,0 +1,41 @@ +package tests.partialValidation.recursiveCases.infixOperations.greaterThanOrEquals + +pipeline test { + // $TEST$ serialization true + »0.5 >= 0.5«; + + // $TEST$ serialization true + »1.5 >= 0«; + + // $TEST$ serialization true + »1 >= 0.5«; + + // $TEST$ serialization true + »1 >= 0«; + + + // $TEST$ serialization false + »0.5 >= 1.5«; + + // $TEST$ serialization false + »0.5 >= 1«; + + // $TEST$ serialization false + »0 >= 1.5«; + + // $TEST$ serialization false + »0 >= 1«; + + + // $TEST$ serialization $unknown + »1 >= true«; + + // $TEST$ serialization $unknown + »false >= 0«; + + // $TEST$ serialization $unknown + »unresolved >= false«; + + // $TEST$ serialization $unknown + »true >= unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/greater than/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/greater than/main.sdstest new file mode 100644 index 000000000..ec98f8136 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/greater than/main.sdstest @@ -0,0 +1,41 @@ +package tests.partialValidation.recursiveCases.infixOperations.greaterThan + +pipeline test { + // $TEST$ serialization true + »1.5 > 0.5«; + + // $TEST$ serialization true + »1.5 > 0«; + + // $TEST$ serialization true + »1 > 0.5«; + + // $TEST$ serialization true + »1 > 0«; + + + // $TEST$ serialization false + »0.5 > 1.5«; + + // $TEST$ serialization false + »0.5 > 1«; + + // $TEST$ serialization false + »0 > 1.5«; + + // $TEST$ serialization false + »0 > 1«; + + + // $TEST$ serialization $unknown + »1 > true«; + + // $TEST$ serialization $unknown + »false > 0«; + + // $TEST$ serialization $unknown + »unresolved > false«; + + // $TEST$ serialization $unknown + »true > unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/identical to/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/identical to/main.sdstest new file mode 100644 index 000000000..78aaa2cc9 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/identical to/main.sdstest @@ -0,0 +1,15 @@ +package tests.partialValidation.recursiveCases.infixOperations.identicalTo + +pipeline test { + // $TEST$ serialization true + »true === true«; + + // $TEST$ serialization false + »false === 1«; + + // $TEST$ serialization $unknown + »1 === unresolved«; + + // $TEST$ serialization $unknown + »unresolved === 1.25«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/less than or equals/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/less than or equals/main.sdstest new file mode 100644 index 000000000..0051f66a5 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/less than or equals/main.sdstest @@ -0,0 +1,41 @@ +package tests.partialValidation.recursiveCases.infixOperations.lessThanOrEquals + +pipeline test { + // $TEST$ serialization true + »0.5 <= 0.5«; + + // $TEST$ serialization true + »0.5 <= 1«; + + // $TEST$ serialization true + »0 <= 1.5«; + + // $TEST$ serialization true + »0 <= 1«; + + + // $TEST$ serialization false + »1.5 <= 0.5«; + + // $TEST$ serialization false + »1.5 <= 0«; + + // $TEST$ serialization false + »1 <= 0.5«; + + // $TEST$ serialization false + »1 <= 0«; + + + // $TEST$ serialization $unknown + »1 <= true«; + + // $TEST$ serialization $unknown + »false <= 0«; + + // $TEST$ serialization $unknown + »unresolved <= false«; + + // $TEST$ serialization $unknown + »true <= unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/less than/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/less than/main.sdstest new file mode 100644 index 000000000..128e39d3f --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/less than/main.sdstest @@ -0,0 +1,41 @@ +package tests.partialValidation.recursiveCases.infixOperations.lessThan + +pipeline test { + // $TEST$ serialization true + »0.5 < 1.5«; + + // $TEST$ serialization true + »0.5 < 1«; + + // $TEST$ serialization true + »0 < 1.5«; + + // $TEST$ serialization true + »0 < 1«; + + + // $TEST$ serialization false + »1.5 < 0.5«; + + // $TEST$ serialization false + »1.5 < 0«; + + // $TEST$ serialization false + »1 < 0.5«; + + // $TEST$ serialization false + »1 < 1«; + + + // $TEST$ serialization $unknown + »1 < true«; + + // $TEST$ serialization $unknown + »false < 0«; + + // $TEST$ serialization $unknown + »unresolved < false«; + + // $TEST$ serialization $unknown + »true < unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/minus/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/minus/main.sdstest new file mode 100644 index 000000000..dfb0dcd1c --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/minus/main.sdstest @@ -0,0 +1,28 @@ +package tests.partialValidation.recursiveCases.infixOperations.minus + +pipeline test { + // $TEST$ serialization 1.25 + »1.5 - 0.25«; + + // $TEST$ serialization 0.5 + »1.5 - 1«; + + // $TEST$ serialization 0.75 + »1 - 0.25«; + + // $TEST$ serialization 0 + »1 - 1«; + + + // $TEST$ serialization $unknown + »true - 1«; + + // $TEST$ serialization $unknown + »1 - true«; + + // $TEST$ serialization $unknown + »unresolved - 1«; + + // $TEST$ serialization $unknown + »1 - unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/not equals/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/not equals/main.sdstest new file mode 100644 index 000000000..b81bc2d12 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/not equals/main.sdstest @@ -0,0 +1,15 @@ +package tests.partialValidation.recursiveCases.infixOperations.notEquals + +pipeline test { + // $TEST$ serialization false + »true != true«; + + // $TEST$ serialization true + »false != 1«; + + // $TEST$ serialization $unknown + »1 != unresolved«; + + // $TEST$ serialization $unknown + »unresolved != 1.25«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/not identical to/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/not identical to/main.sdstest new file mode 100644 index 000000000..62d94c335 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/not identical to/main.sdstest @@ -0,0 +1,15 @@ +package tests.partialValidation.recursiveCases.infixOperations.notIdenticalTo + +pipeline test { + // $TEST$ serialization false + »true !== true«; + + // $TEST$ serialization true + »false !== 1«; + + // $TEST$ serialization $unknown + »1 !== unresolved«; + + // $TEST$ serialization $unknown + »unresolved !== 1.25«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/or/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/or/main.sdstest new file mode 100644 index 000000000..433f0bf6c --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/or/main.sdstest @@ -0,0 +1,27 @@ +package tests.partialValidation.recursiveCases.infixOperations.`or` + +pipeline test { + // $TEST$ serialization false + »false or false«; + + // $TEST$ serialization true + »false or true«; + + // $TEST$ serialization true + »true or false«; + + // $TEST$ serialization true + »true or true«; + + // $TEST$ serialization $unknown + »1 or true«; + + // $TEST$ serialization $unknown + »false or 0«; + + // $TEST$ serialization $unknown + »unresolved or false«; + + // $TEST$ serialization $unknown + »true or unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/plus/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/plus/main.sdstest new file mode 100644 index 000000000..23551d74d --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/plus/main.sdstest @@ -0,0 +1,28 @@ +package tests.partialValidation.recursiveCases.infixOperations.plus + +pipeline test { + // $TEST$ serialization 1.75 + »1.5 + 0.25«; + + // $TEST$ serialization 2.5 + »1.5 + 1«; + + // $TEST$ serialization 1.25 + »1 + 0.25«; + + // $TEST$ serialization 2 + »1 + 1«; + + + // $TEST$ serialization $unknown + »true + 1«; + + // $TEST$ serialization $unknown + »1 + true«; + + // $TEST$ serialization $unknown + »unresolved + 1«; + + // $TEST$ serialization $unknown + »1 + unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/infix operations/times/main.sdstest b/tests/resources/partial evaluation/recursive cases/infix operations/times/main.sdstest new file mode 100644 index 000000000..b23b220e8 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/infix operations/times/main.sdstest @@ -0,0 +1,28 @@ +package tests.partialValidation.recursiveCases.infixOperations.times + +pipeline test { + // $TEST$ serialization 0.75 + »1.5 * 0.5«; + + // $TEST$ serialization 1.5 + »1.5 * 1«; + + // $TEST$ serialization 0.25 + »1 * 0.25«; + + // $TEST$ serialization 1 + »1 * 1«; + + + // $TEST$ serialization $unknown + »true * 1«; + + // $TEST$ serialization $unknown + »1 * true«; + + // $TEST$ serialization $unknown + »unresolved * 1«; + + // $TEST$ serialization $unknown + »1 * unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/lists/main.sdstest b/tests/resources/partial evaluation/recursive cases/lists/main.sdstest new file mode 100644 index 000000000..112029fa0 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/lists/main.sdstest @@ -0,0 +1,9 @@ +package tests.partialValidation.recursiveCases.lists + +pipeline test { + // $TEST$ serialization [] + »[]«; + + // $TEST$ serialization [1, 2.5, null, $unknown] + »[1, 2.5, null, unresolved]«; +} diff --git a/tests/resources/partial evaluation/recursive cases/maps/main.sdstest b/tests/resources/partial evaluation/recursive cases/maps/main.sdstest new file mode 100644 index 000000000..22e69d284 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/maps/main.sdstest @@ -0,0 +1,15 @@ +package tests.partialValidation.recursiveCases.maps + +pipeline test { + // $TEST$ serialization {} + »{}«; + + // $TEST$ serialization {"a": 1, "b": 2.5, "c": null, "d": $unknown, $unknown: true} + »{ + "a": 1, + "b": 2.5, + "c": null, + "d": unresolved, + unresolved: true, + }«; +} diff --git a/tests/resources/partial evaluation/recursive cases/member accesses/of enum variants/main.sdstest b/tests/resources/partial evaluation/recursive cases/member accesses/of enum variants/main.sdstest new file mode 100644 index 000000000..35c22e992 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/member accesses/of enum variants/main.sdstest @@ -0,0 +1,16 @@ +package tests.partialValidation.recursiveCases.memberAccesses.ofEnumVariants + +enum MyEnum { + MyEnumVariantWithoutParameters + MyEnumVariantWithParameters(p: Int, q: Int = 3) +} + +fun f() -> r: Int + +pipeline test { + // $TEST$ serialization MyEnumVariantWithoutParameters + »MyEnum.MyEnumVariantWithoutParameters«; + + // $TEST$ serialization MyEnumVariantWithParameters + »MyEnum.MyEnumVariantWithParameters«; +} diff --git a/tests/resources/partial evaluation/recursive cases/member accesses/unresolved/main.sdstest b/tests/resources/partial evaluation/recursive cases/member accesses/unresolved/main.sdstest new file mode 100644 index 000000000..c57c51313 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/member accesses/unresolved/main.sdstest @@ -0,0 +1,13 @@ +package tests.partialValidation.recursiveCases.memberAccesses.unresolved + +enum MyEnum { + MyEnumVariant +} + +pipeline test { + // $TEST$ serialization $unknown + »unresolved.MyEnumVariant«; + + // $TEST$ serialization $unknown + »MyEnum.unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/parenthesized expressions/main.sdstest b/tests/resources/partial evaluation/recursive cases/parenthesized expressions/main.sdstest new file mode 100644 index 000000000..81abee4d9 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/parenthesized expressions/main.sdstest @@ -0,0 +1,11 @@ +package tests.partialValidation.recursiveCases.parenthesizedExpression + +pipeline test { + // $TEST$ equivalence_class value1 + // $TEST$ equivalence_class value1 + »(»1«)«; + + // $TEST$ equivalence_class value2 + // $TEST$ equivalence_class value2 + »(»null«)«; +} diff --git a/tests/resources/partial evaluation/recursive cases/prefix operations/minus/main.sdstest b/tests/resources/partial evaluation/recursive cases/prefix operations/minus/main.sdstest new file mode 100644 index 000000000..383a0125b --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/prefix operations/minus/main.sdstest @@ -0,0 +1,15 @@ +package tests.partialValidation.recursiveCases.prefixOperations.minus + +pipeline test { + // $TEST$ serialization -1.5 + »-1.5«; + + // $TEST$ serialization -1 + »-1«; + + // $TEST$ serialization $unknown + »-true«; + + // $TEST$ serialization $unknown + »-unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/prefix operations/not/main.sdstest b/tests/resources/partial evaluation/recursive cases/prefix operations/not/main.sdstest new file mode 100644 index 000000000..3a692a1e1 --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/prefix operations/not/main.sdstest @@ -0,0 +1,15 @@ +package tests.partialValidation.recursiveCases.prefixOperations.`not` + +pipeline test { + // $TEST$ serialization true + »not false«; + + // $TEST$ serialization false + »not true«; + + // $TEST$ serialization $unknown + »not 0«; + + // $TEST$ serialization $unknown + »not unresolved«; +} diff --git a/tests/resources/partial evaluation/recursive cases/template strings/main.sdstest b/tests/resources/partial evaluation/recursive cases/template strings/main.sdstest new file mode 100644 index 000000000..88f5e306c --- /dev/null +++ b/tests/resources/partial evaluation/recursive cases/template strings/main.sdstest @@ -0,0 +1,12 @@ +package tests.partialValidation.recursiveCases.templateStrings + +pipeline test { + // $TEST$ serialization "start 1 inner1 true inner2 test end" + »"start {{ 1 }} inner1 {{ true }} inner2 {{ "test" }} end"«; + + // $TEST$ serialization "start 1 inner1 true inner2 test end " + »"start\t{{ 1 }} inner1\t{{ true }} inner2\t{{ "test" }} end\t"«; + + // $TEST$ serialization $unknown + »"start {{ call() }} end"«; +} diff --git a/tests/resources/partial evaluation/simple recursive cases/template string/main.sdstest b/tests/resources/partial evaluation/simple recursive cases/template string/main.sdstest deleted file mode 100644 index 421f84124..000000000 --- a/tests/resources/partial evaluation/simple recursive cases/template string/main.sdstest +++ /dev/null @@ -1,12 +0,0 @@ -package tests.partialValidation.simpleRecursiveCases.templateString - -pipeline test { - // $TEST$ constant serialization "start 1 inner1 true inner2 test end" - »"start {{ 1 }} inner1 {{ true }} inner2 {{ "test" }} end"«; - - // $TEST$ constant serialization "start 1 inner1 true inner2 test end " - »"start\t{{ 1 }} inner1\t{{ true }} inner2\t{{ "test" }} end\t"«; - - // $TEST$ not constant - »"start {{ call() }} end"«; -} diff --git a/tests/resources/partial evaluation/skip-old/ToConstantExpressionTest.kt b/tests/resources/partial evaluation/skip-old/ToConstantExpressionTest.kt new file mode 100644 index 000000000..01519d952 --- /dev/null +++ b/tests/resources/partial evaluation/skip-old/ToConstantExpressionTest.kt @@ -0,0 +1,413 @@ +//@ExtendWith(InjectionExtension::class) +//@InjectWith(SafeDSInjectorProvider::class) +//class ToConstantExpressionTest { +// +// @Inject +// private lateinit var parseHelper: ParseHelper +// +// private val factory = SafeDSFactory.eINSTANCE +// +// private lateinit var impureBlockLambda: SdsBlockLambda +// private lateinit var pureBlockLambda: SdsBlockLambda +// private lateinit var recursiveBlockLambda: SdsBlockLambda +// private lateinit var impureExpressionLambda: SdsExpressionLambda +// private lateinit var pureExpressionLambda: SdsExpressionLambda +// private lateinit var recursiveExpressionLambda: SdsExpressionLambda +// private lateinit var impureStep: SdsStep +// private lateinit var pureStep: SdsStep +// private lateinit var recursiveStep: SdsStep +// +// @BeforeEach +// fun reset() { +// val compilationUnit = parseHelper.parseResource("partialEvaluation/callables.sdstest") +// compilationUnit.shouldNotBeNull() +// +// val blockLambdas = compilationUnit.descendants().toList() +// blockLambdas.shouldHaveSize(3) +// +// impureBlockLambda = blockLambdas[0] +// pureBlockLambda = blockLambdas[1] +// recursiveBlockLambda = blockLambdas[2] +// +// val expressionLambdas = compilationUnit.descendants().toList() +// expressionLambdas.shouldHaveSize(3) +// +// impureExpressionLambda = expressionLambdas[0] +// pureExpressionLambda = expressionLambdas[1] +// recursiveExpressionLambda = expressionLambdas[2] +// +// impureStep = compilationUnit.findUniqueDeclarationOrFail("impureStep") +// pureStep = compilationUnit.findUniqueDeclarationOrFail("pureStep") +// recursiveStep = compilationUnit.findUniqueDeclarationOrFail("recursiveStep") +// } +// +// @Nested +// inner class BaseCases { +// +// @Test +// fun `toConstantExpression should return null for block lambda`() { +// val testData = createSdsBlockLambda() +// testData.toConstantExpressionOrNull().shouldBeNull() +// } +// +// @Test +// fun `simplify should return null for impure block lambda`() { +// impureBlockLambda.simplify(emptyMap()).shouldBeNull() +// } +// +// @Test +// fun `simplify should return intermediate block lambda for pure block lambda`() { +// pureBlockLambda.simplify(emptyMap()).shouldBeInstanceOf() +// } +// +// @Test +// fun `simplify should return null for block lambda with recursive call`() { +// recursiveBlockLambda.simplify(emptyMap()).shouldBeNull() +// } +// +// @Test +// fun `toConstantExpression should return null for expression lambda`() { +// val testData = createSdsExpressionLambda(result = createSdsNull()) +// testData.toConstantExpressionOrNull().shouldBeNull() +// } +// +// @Test +// fun `simplify should return null for impure expression lambda`() { +// impureExpressionLambda.simplify(emptyMap()).shouldBeNull() +// } +// +// @Test +// fun `simplify should return intermediate expression lambda for pure expression lambda`() { +// pureExpressionLambda.simplify(emptyMap()).shouldBeInstanceOf() +// } +// +// @Test +// fun `simplify should return null for expression lambda with recursive call`() { +// recursiveExpressionLambda.simplify(emptyMap()).shouldBeNull() +// } +// } + +// @Nested +// inner class Call { +// +// private lateinit var compilationUnit: SdsCompilationUnit +// +// @BeforeEach +// fun reset() { +// compilationUnit = parseHelper.parseResource("partialEvaluation/calls.sdstest")!! +// } +// +// @Test +// fun `should evaluate calls of block lambdas`() { +// val pipeline = compilationUnit.findUniqueDeclarationOrFail("callToBlockLambda") +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(1) +// } +// +// @Test +// fun `should evaluate calls of expression lambdas`() { +// val pipeline = compilationUnit.findUniqueDeclarationOrFail("callToExpressionLambda") +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(1) +// } +// +// @Test +// fun `should evaluate calls of steps`() { +// val pipeline = compilationUnit.findUniqueDeclarationOrFail("callToStep") +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(1) +// } +// +// @Test +// fun `should evaluate calls of steps with variadic parameter`() { +// val pipeline = compilationUnit.findUniqueDeclarationOrFail("callToStepWithVariadicParameter") +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull().shouldBeNull() +// } +// +// @Test +// fun `should evaluate calls of steps with indexed variadic parameter`() { +// val pipeline = compilationUnit +// .findUniqueDeclarationOrFail("callToStepWithIndexedVariadicParameter") +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(1) +// } +// +// @Test +// fun `should substitute parameters that were bound at call of a lambda`() { +// val pipeline = compilationUnit.findUniqueDeclarationOrFail( +// "parameterAssignedDuringCall", +// ) +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(10) +// } +// +// @Test +// fun `should substitute parameters that were bound at creation of a lambda`() { +// val pipeline = compilationUnit.findUniqueDeclarationOrFail( +// "parameterAssignedDuringCreationOfLambda", +// ) +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(1) +// } +// +// @Test +// fun `should evaluate calls with lambda as parameter`() { +// val pipeline = compilationUnit.findUniqueDeclarationOrFail("lambdaAsParameter") +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(1) +// } +// +// @Test +// fun `should return null otherwise`() { +// val testData = createSdsCall(receiver = createSdsNull()) +// testData.toConstantExpressionOrNull().shouldBeNull() +// } +// } +// +// @Nested +// inner class MemberAccess { +// @Test +// fun `should return constant null if receiver is constant null and member access is null safe`() { +// val testData = createSdsMemberAccess( +// receiver = createSdsNull(), +// member = createSdsReference(createSdsAttribute("testAttribute")), +// isNullSafe = true, +// ) +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantNull +// } +// +// @Test +// fun `should return null if receiver is constant null and member access is not null safe`() { +// val testData = createSdsMemberAccess( +// receiver = createSdsNull(), +// member = createSdsReference(createSdsAttribute("testAttribute")), +// ) +// +// testData.toConstantExpressionOrNull().shouldBeNull() +// } +// +// @Test +// fun `should access the result of a call by name if result exists`() { +// val compilationUnit = +// parseHelper.parseResource("partialEvaluation/memberAccesses.sdstest") +// compilationUnit.shouldNotBeNull() +// +// val pipeline = compilationUnit.findUniqueDeclarationOrFail("successfulResultAccess") +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(1) +// } +// +// @Test +// fun `should return null if accessed result does not exist`() { +// val compilationUnit = +// parseHelper.parseResource("partialEvaluation/memberAccesses.sdstest") +// compilationUnit.shouldNotBeNull() +// +// val pipeline = compilationUnit.findUniqueDeclarationOrFail("failedResultAccess") +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull().shouldBeNull() +// } +// +// @Test +// fun `should return null for other receivers`() { +// val testData = createSdsMemberAccess( +// receiver = createSdsInt(1), +// member = createSdsReference( +// createSdsEnumVariant( +// name = "TestEnumVariant", +// parameters = listOf( +// createSdsParameter(name = "testParameter"), +// ), +// ), +// ), +// ) +// +// testData.toConstantExpressionOrNull().shouldBeNull() +// } +// } +// +// @Nested +// inner class Reference { +// @Test +// fun `should convert assigned value of referenced placeholder`() { +// val testPlaceholder = createSdsPlaceholder("testPlaceholder") +// createSdsAssignment( +// assignees = listOf(testPlaceholder), +// createSdsNull(), +// ) +// val testData = createSdsReference( +// declaration = testPlaceholder, +// ) +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantNull +// } +// +// @Test +// fun `should return null if referenced placeholder has no assigned value`() { +// val testData = createSdsReference( +// declaration = createSdsPlaceholder("testPlaceholder"), +// ) +// +// testData.toConstantExpressionOrNull().shouldBeNull() +// } +// +// @Test +// fun `simplify should return substituted value if it exists`() { +// val testParameter = createSdsParameter("testParameter") +// val testData = createSdsReference( +// declaration = testParameter, +// ) +// +// testData.simplify(mapOf(testParameter to SdsConstantNull)) shouldBe SdsConstantNull +// } +// +// @Test +// fun `simplify should return default value if referenced parameter is not substituted but optional`() { +// val testParameter = createSdsParameter( +// name = "testParameter", +// defaultValue = createSdsNull(), +// ) +// val testData = createSdsReference( +// declaration = testParameter, +// ) +// +// testData.simplify(emptyMap()) shouldBe SdsConstantNull +// } +// +// @Test +// fun `simplify should return null if referenced parameter is required and not substituted`() { +// val testParameter = createSdsParameter("testParameter") +// val testData = createSdsReference( +// declaration = testParameter, +// ) +// +// testData.simplify(emptyMap()).shouldBeNull() +// } +// +// @Test +// fun `toConstantExpression should return null if step is referenced`() { +// val testData = createSdsReference(createSdsStep("testStep")) +// testData.toConstantExpressionOrNull().shouldBeNull() +// } +// +// @Test +// fun `simplify should return null if referenced step is impure`() { +// val testData = createSdsReference(impureStep) +// testData.simplify(emptyMap()).shouldBeNull() +// } +// +// @Test +// fun `simplify should return intermediate step if referenced step is pure`() { +// val testData = createSdsReference(pureStep) +// testData.simplify(emptyMap()).shouldBeInstanceOf() +// } +// +// @Test +// fun `simplify should return null if referenced step has recursive calls`() { +// val testData = createSdsReference(recursiveStep) +// testData.simplify(emptyMap()).shouldBeNull() +// } +// +// @Test +// fun `should return value of placeholders inside valid assignment with call as expression`() { +// val compilationUnit = +// parseHelper.parseResource("partialEvaluation/references.sdstest") +// compilationUnit.shouldNotBeNull() +// +// val pipeline = compilationUnit.findUniqueDeclarationOrFail("successfulRecordAssignment") +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(1) +// } +// +// @Test +// fun `should return null for references to placeholders inside invalid assignment with call as expression`() { +// val compilationUnit = +// parseHelper.parseResource("partialEvaluation/references.sdstest") +// compilationUnit.shouldNotBeNull() +// +// val pipeline = compilationUnit.findUniqueDeclarationOrFail("failedRecordAssignment") +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull().shouldBeNull() +// } +// +// @Test +// fun `should evaluate references to placeholders (assigned, called step has different yield order)`() { +// val compilationUnit = +// parseHelper.parseResource("partialEvaluation/references.sdstest") +// compilationUnit.shouldNotBeNull() +// +// val pipeline = compilationUnit.findUniqueDeclarationOrFail( +// "recordAssignmentWithDifferentYieldOrder", +// ) +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(1) +// } +// +// @Test +// fun `should evaluate references to placeholders (assigned, called step has missing yield)`() { +// val compilationUnit = +// parseHelper.parseResource("partialEvaluation/references.sdstest") +// compilationUnit.shouldNotBeNull() +// +// val pipeline = compilationUnit.findUniqueDeclarationOrFail("recordAssignmentWithMissingYield") +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(1) +// } +// +// @Test +// fun `should evaluate references to placeholders (assigned, called step has additional yield)`() { +// val compilationUnit = +// parseHelper.parseResource("partialEvaluation/references.sdstest") +// compilationUnit.shouldNotBeNull() +// +// val pipeline = compilationUnit.findUniqueDeclarationOrFail( +// "recordAssignmentWithAdditionalYield", +// ) +// val testData = pipeline.expectedExpression() +// +// testData.toConstantExpressionOrNull() shouldBe SdsConstantInt(1) +// } +// +// @Test +// fun `should return null for other declarations`() { +// val testData = createSdsReference( +// declaration = createSdsAnnotation("TestAnnotation"), +// ) +// +// testData.toConstantExpressionOrNull().shouldBeNull() +// } +// } +//} +// +//private fun Double.toSdsNumber(): SdsAbstractExpression { +// return when { +// this == this.toInt().toDouble() -> createSdsInt(this.toInt()) +// else -> createSdsFloat(this) +// } +//} +// +///** +// * Helper method for tests loaded from a resource that returns the expression of the first expression statement in the +// * pipeline. +// */ +//private fun SdsPipeline.expectedExpression() = statementsOrEmpty() +// .filterIsInstance() +// .firstOrNull() +// .shouldNotBeNull() +// .expression diff --git a/tests/resources/partial evaluation/skip-old/callables.sdstest b/tests/resources/partial evaluation/skip-old/callables.sdstest new file mode 100644 index 000000000..1131ae960 --- /dev/null +++ b/tests/resources/partial evaluation/skip-old/callables.sdstest @@ -0,0 +1,42 @@ +package tests.partialEvaluation.callables + +// Preparation ----------------------------------------------------------------- + +fun impureFunction() -> result: Int + +@Pure +fun pureFunction() -> result: Int + +// Test data ------------------------------------------------------------------- + +pipeline pipelineWithImpureAndPureLambdas { + val impureBlockLambda = () { + impureFunction(); + }; + + val pureBlockLambda = () { + pureFunction(); + }; + + val recursiveBlockLambda = () { + recursiveStep(); + }; + + val impureExpressionLambda = () -> impureFunction(); + + val pureExpressionLambda = () -> pureFunction(); + + val recursiveExpressionLambda = () -> recursiveStep(); +} + +step impureStep() { + impureFunction(); +} + +step pureStep() { + pureFunction(); +} + +step recursiveStep() -> result: Int { + yield result = recursiveStep(); +} diff --git a/tests/resources/partial evaluation/skip-old/calls.sdstest b/tests/resources/partial evaluation/skip-old/calls.sdstest new file mode 100644 index 000000000..e9536e7ab --- /dev/null +++ b/tests/resources/partial evaluation/skip-old/calls.sdstest @@ -0,0 +1,56 @@ +package tests.partialEvaluation.higherOrder + +pipeline callToBlockLambda { + val lambda = (a) { yield result = a; }; + lambda(1); +} + +pipeline callToExpressionLambda { + val lambda = (a) -> a; + lambda(1); +} + +step myStep1(a: Int) -> result: Int { + yield result = a; +} + +pipeline callToStep { + myStep1(1); +} + +step myStep2(vararg params: Int) -> result: Int { + yield result = params; +} + +pipeline callToStepWithVariadicParameter { + myStep2(1); +} + +step myStep3(vararg params: Int) -> result: Int { + yield result = params[0]; +} + +pipeline callToStepWithIndexedVariadicParameter { + myStep3(1); +} + +pipeline parameterAssignedDuringCall { + ((a, b) { + val d = b; + yield result = ((b, c) -> a + b + c + d)(1, 2); + })(3, 4); +} + +step myStep4(param: Int) -> f: () -> (result: Int) { + yield f = () -> param; +} + +pipeline parameterAssignedDuringCreationOfLambda { + myStep4(1)(); +} + +pipeline lambdaAsParameter { + val apply = (f) -> f(); + + apply(() -> 1); +} diff --git a/tests/resources/partial evaluation/skip-old/memberAccesses.sdstest b/tests/resources/partial evaluation/skip-old/memberAccesses.sdstest new file mode 100644 index 000000000..21ada836d --- /dev/null +++ b/tests/resources/partial evaluation/skip-old/memberAccesses.sdstest @@ -0,0 +1,17 @@ +package tests.partialEvaluation.memberAccesses + +pipeline successfulResultAccess { + val lambda = () { + yield result = 1; + }; + + lambda().result; +} + +pipeline failedResultAccess { + val lambda = () { + yield result = 1; + }; + + lambda().result1; +} diff --git a/tests/resources/partial evaluation/skip-old/references.sdstest b/tests/resources/partial evaluation/skip-old/references.sdstest new file mode 100644 index 000000000..b90ce668d --- /dev/null +++ b/tests/resources/partial evaluation/skip-old/references.sdstest @@ -0,0 +1,51 @@ +package tests.partialEvaluation.references + +pipeline successfulRecordAssignment { + val lambda = () { + yield result = 1; + }; + val placeholder = lambda(); + + placeholder; +} + +pipeline failedRecordAssignment { + val lambda = () { + yield result = 1; + }; + _, val placeholder = lambda(); + + placeholder; +} + +step myStep1() -> (a: Int, b: Int) { + yield b = 1; + yield a = 2; +} + +pipeline recordAssignmentWithDifferentYieldOrder { + val placeholder1, val placeholder2 = myStep1(); + + placeholder1 - placeholder2; +} + +step myStep2() -> (a: Int, b: Int) { + yield b = 1; +} + +pipeline recordAssignmentWithMissingYield { + _, val placeholder = myStep2(); + + placeholder; +} + +step myStep3() -> (a: Int) { + yield b = 2; + yield a = 1; +} + +pipeline recordAssignmentWithAdditionalYield { + val placeholder = myStep3(); + + placeholder; +}