Skip to content

Commit 8373595

Browse files
entlichertzezula
authored andcommitted
[GR-70086] Added replacementOf and replacementMethod attributes to message library generator.
PullRequest: graal/22238
2 parents 0079a73 + 9659e8f commit 8373595

File tree

24 files changed

+1103
-93
lines changed

24 files changed

+1103
-93
lines changed

truffle/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ This changelog summarizes major changes between Truffle versions relevant to lan
3535
* GR-51945: Added option `engine.TraceBytecode` to enable printing each executed Bytecode DSL instruction. Use the `engine.BytecodeMethodFilter` option to print instructions only for a given method.
3636
* GR-51945: Added option `engine.BytecodeHistogram` to enable printing a bytecode histogram on engine close. Use `engine.BytecodeHistogramInterval` to configure the interval at which the histogram is reset and printed.
3737

38+
* GR-70086: Added `replacementOf` and `replacementMethod` attributes to `GenerateLibrary.Abstract` annotation. They enable automatic generation of legacy delegators during message library evolution, while allowing custom conversions when needed.
39+
* GR-70086 Deprecated `Message.resolve(Class<?>, String)`. Use `Message.resolveExact(Class<?>, String, Class<?>...)` with argument types instead. This deprecation was necessary as library messages are no longer unique by message name, if the previous message was deprecated.
3840

3941

4042
## Version 25.0

truffle/src/com.oracle.truffle.api.library.test/src/com/oracle/truffle/api/library/test/GenerateLibraryTest.java

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@
4141
package com.oracle.truffle.api.library.test;
4242

4343
import static org.junit.Assert.assertEquals;
44+
import static org.junit.Assert.assertTrue;
4445
import static org.junit.Assert.fail;
4546

47+
import com.oracle.truffle.api.TruffleLanguage;
4648
import org.junit.Test;
4749

4850
import com.oracle.truffle.api.dsl.Cached;
@@ -53,6 +55,8 @@
5355
import com.oracle.truffle.api.library.GenerateLibrary.Abstract;
5456
import com.oracle.truffle.api.library.GenerateLibrary.DefaultExport;
5557
import com.oracle.truffle.api.library.Library;
58+
import com.oracle.truffle.api.library.LibraryFactory;
59+
import com.oracle.truffle.api.library.Message;
5660
import com.oracle.truffle.api.library.test.CachedLibraryTest.SimpleDispatchedNode;
5761
import com.oracle.truffle.api.nodes.Node;
5862
import com.oracle.truffle.api.test.AbstractLibraryTest;
@@ -302,6 +306,340 @@ public abstract static class ValidStringLibrary extends Library {
302306

303307
}
304308

309+
@Test
310+
@SuppressWarnings("deprecation")
311+
public void testReplacementMessageReflection() {
312+
assertEquals(String.class, Message.resolve(ReplacementsLibrary.class, "readMember").getParameterType(1));
313+
assertTrue(Message.resolve(ReplacementsLibrary.class, "readMember").isDeprecated());
314+
}
315+
316+
@GenerateLibrary
317+
public abstract static class ReplacementsLibrary extends Library {
318+
319+
@Deprecated
320+
public int readMember(Object receiver, String name) {
321+
throw new UnsupportedOperationException();
322+
}
323+
324+
@Abstract(replacementOf = "readMember(Object, String)")
325+
public int readMember(Object receiver, Object name) {
326+
if (name instanceof String stringName) {
327+
return readMember(receiver, stringName);
328+
}
329+
throw new UnsupportedOperationException();
330+
}
331+
332+
@Deprecated
333+
public int read(Object receiver, int index) {
334+
throw new UnsupportedOperationException();
335+
}
336+
337+
@Abstract(replacementOf = "read(Object, int)")
338+
public int read(Object receiver, long index) {
339+
if (Integer.MIN_VALUE <= index && index <= Integer.MAX_VALUE) {
340+
return read(receiver, (int) index);
341+
}
342+
throw new UnsupportedOperationException();
343+
}
344+
345+
@Deprecated
346+
public int readUnsigned(Object receiver, int index) {
347+
throw new UnsupportedOperationException();
348+
}
349+
350+
@Abstract(replacementOf = "readUnsigned(Object, int)", replacementMethod = "readUnsignedLegacy")
351+
public int readUnsigned(Object receiver, long index) {
352+
if (0 <= index && index <= 0xFFFFFFFFL) {
353+
return readUnsigned(receiver, (int) (0xFFFFFFFFL & index));
354+
}
355+
throw new UnsupportedOperationException();
356+
}
357+
358+
protected final int readUnsignedLegacy(Object receiver, int index) {
359+
long unsignedIndex = Integer.toUnsignedLong(index);
360+
return read(receiver, unsignedIndex);
361+
}
362+
}
363+
364+
@ExportLibrary(ReplacementsLibrary.class)
365+
@SuppressWarnings({"deprecation", "static-method"})
366+
public static class ReplacementLegacy {
367+
368+
@ExportMessage
369+
final int readMember(String name) {
370+
return 1;
371+
}
372+
373+
@ExportMessage
374+
final int read(int index) {
375+
return Integer.toString(index).length();
376+
}
377+
378+
@ExportMessage
379+
final int readUnsigned(int index) {
380+
return Integer.toUnsignedString(index).length();
381+
}
382+
}
383+
384+
@ExportLibrary(ReplacementsLibrary.class)
385+
@SuppressWarnings("static-method")
386+
public static class ReplacementNew {
387+
388+
@ExportMessage
389+
final int readMember(Object name) {
390+
return 100;
391+
}
392+
393+
@ExportMessage
394+
final int read(long index) {
395+
return Long.toString(index).length();
396+
}
397+
398+
@ExportMessage
399+
final int readUnsigned(long index) {
400+
return Long.toUnsignedString(index).length();
401+
}
402+
}
403+
404+
@ExportLibrary(ReplacementsLibrary.class)
405+
@SuppressWarnings("static-method")
406+
public static class ReplacementLegacyAndNew {
407+
408+
@ExportMessage
409+
final int readMember(Object name) {
410+
return 100;
411+
}
412+
413+
@ExpectError("Cannot export both a deprecated message and a new message 'read' that declares a replacement for it. " +
414+
"Remove the @ExportMessage annotation from the deprecated methods to resolve this problem.")
415+
@ExportMessage
416+
final int read(int index) {
417+
return Long.toString(index).length();
418+
}
419+
420+
@ExpectError("Cannot export both a deprecated message and a new message 'read' that declares a replacement for it. " +
421+
"Remove the @ExportMessage annotation from the deprecated methods to resolve this problem.")
422+
@ExportMessage
423+
final int read(long index) {
424+
return Long.toString(index).length();
425+
}
426+
427+
@ExpectError("Cannot export both a deprecated message and a new message 'readUnsigned' that declares a replacement for it. " +
428+
"Remove the @ExportMessage annotation from the deprecated methods to resolve this problem.")
429+
@ExportMessage
430+
final int readUnsigned(int index) {
431+
return Integer.toUnsignedString(index).length();
432+
}
433+
434+
@ExpectError("Cannot export both a deprecated message and a new message 'readUnsigned' that declares a replacement for it. " +
435+
"Remove the @ExportMessage annotation from the deprecated methods to resolve this problem.")
436+
@ExportMessage
437+
final int readUnsigned(long index) {
438+
return Long.toUnsignedString(index).length();
439+
}
440+
}
441+
442+
@Test
443+
@SuppressWarnings("deprecation")
444+
public void testReplacements() {
445+
ReplacementsLibrary lib = LibraryFactory.resolve(ReplacementsLibrary.class).getUncached();
446+
Object legacyObj = new ReplacementLegacy();
447+
Object newObj = new ReplacementNew();
448+
449+
assertEquals(1, lib.readMember(legacyObj, "string"));
450+
assertEquals(1, lib.readMember(legacyObj, (Object) "string"));
451+
assertEquals(2, lib.read(legacyObj, 10));
452+
assertEquals(2, lib.read(legacyObj, 10L));
453+
assertEquals(10, lib.readUnsigned(legacyObj, -10));
454+
assertEquals(10, lib.readUnsigned(legacyObj, 0XFFFFFFFA));
455+
456+
assertEquals(100, lib.readMember(newObj, "string"));
457+
assertEquals(100, lib.readMember(newObj, (Object) "string"));
458+
assertEquals(2, lib.read(newObj, 10));
459+
assertEquals(2, lib.read(newObj, 10L));
460+
assertEquals(10, lib.readUnsigned(newObj, -10));
461+
assertEquals(20, lib.readUnsigned(newObj, -10L));
462+
}
463+
464+
@GenerateLibrary
465+
public abstract static class ReplacementsLibrary2 extends Library {
466+
467+
@Deprecated
468+
public Class<? extends TruffleLanguage<?>> getLanguage(Object receiver) {
469+
throw new UnsupportedOperationException();
470+
}
471+
472+
@Abstract(replacementOf = "getLanguage(Object)", replacementMethod = "getLanguageImpl")
473+
public String getLanguageId(Object receiver) {
474+
return TestLanguage.ID;
475+
}
476+
477+
@SuppressWarnings("static-method")
478+
protected final Class<? extends TruffleLanguage<?>> getLanguageImpl(Object receiver) {
479+
return TestLanguage.class;
480+
}
481+
}
482+
483+
private static final class TestLanguage extends TruffleLanguage<TruffleLanguage.Env> {
484+
static final String ID = "test-language-id";
485+
486+
@Override
487+
protected Env createContext(Env env) {
488+
return env;
489+
}
490+
}
491+
492+
@ExportLibrary(ReplacementsLibrary2.class)
493+
@SuppressWarnings({"deprecation", "static-method"})
494+
public static class ReplacementLegacy2 {
495+
496+
@ExportMessage
497+
public Class<? extends TruffleLanguage<?>> getLanguage() {
498+
return TestLanguage.class;
499+
}
500+
}
501+
502+
@ExportLibrary(ReplacementsLibrary2.class)
503+
@SuppressWarnings("static-method")
504+
public static class ReplacementNew2 {
505+
506+
@ExportMessage
507+
public String getLanguageId() {
508+
return TestLanguage.ID;
509+
}
510+
}
511+
512+
@ExportLibrary(ReplacementsLibrary2.class)
513+
@SuppressWarnings({"deprecation", "static-method"})
514+
public static class ReplacementLegacyAndNew2 {
515+
516+
@ExpectError("Cannot export both a deprecated message 'getLanguage' and a new message 'getLanguageId' that declares a replacement for it. " +
517+
"Remove the @ExportMessage annotation from the deprecated methods to resolve this problem.")
518+
@ExportMessage
519+
public Class<? extends TruffleLanguage<?>> getLanguage() {
520+
return TestLanguage.class;
521+
}
522+
523+
@ExportMessage
524+
public String getLanguageId() {
525+
return TestLanguage.ID;
526+
}
527+
}
528+
529+
@GenerateLibrary
530+
public abstract static class ReplacementsLibraryErrors1 extends Library {
531+
532+
@Deprecated
533+
public int readMember(Object receiver, String name) {
534+
throw new UnsupportedOperationException();
535+
}
536+
537+
@ExpectError("The replaced message readMember(Object, int) was not found. Specify an existing message with optional type arguments.")
538+
@Abstract(replacementOf = "readMember(Object, int)")
539+
public int readMember(Object receiver, Object name) {
540+
if (name instanceof String stringName) {
541+
return readMember(receiver, stringName);
542+
}
543+
throw new UnsupportedOperationException();
544+
}
545+
}
546+
547+
@GenerateLibrary
548+
@SuppressWarnings({"deprecation", "static-method"})
549+
public abstract static class ReplacementsLibraryErrors2 extends Library {
550+
551+
@Deprecated
552+
public int readUnsigned(Object receiver, int index) {
553+
throw new UnsupportedOperationException();
554+
}
555+
556+
@ExpectError("The replacement method readUnsignedLegacy does not have signature and thrown types equal to the message readUnsigned(Object, int) it replaces.")
557+
@Abstract(replacementOf = "readUnsigned(Object, int)", replacementMethod = "readUnsignedLegacy")
558+
public int readUnsigned(Object receiver, long index) {
559+
if (0 <= index && index <= 0xFFFFFFFFL) {
560+
return readUnsigned(receiver, (int) (0xFFFFFFFFL & index));
561+
}
562+
throw new UnsupportedOperationException();
563+
}
564+
565+
protected final int readUnsignedLegacy(Object receiver, String index) {
566+
return index.length();
567+
}
568+
}
569+
570+
@GenerateLibrary
571+
@SuppressWarnings({"deprecation", "static-method"})
572+
public abstract static class ReplacementsLibraryErrors3 extends Library {
573+
574+
@Deprecated
575+
public int readUnsigned(Object receiver, int index) throws Exception {
576+
throw new UnsupportedOperationException();
577+
}
578+
579+
@ExpectError("The replacement method readUnsignedLegacy does not have signature and thrown types equal to the message readUnsigned(Object, int) it replaces.")
580+
@Abstract(replacementOf = "readUnsigned(Object, int)", replacementMethod = "readUnsignedLegacy")
581+
public int readUnsigned(Object receiver, long index) throws Exception {
582+
if (0 <= index && index <= 0xFFFFFFFFL) {
583+
return readUnsigned(receiver, (int) (0xFFFFFFFFL & index));
584+
}
585+
throw new UnsupportedOperationException();
586+
}
587+
588+
protected final int readUnsignedLegacy(Object receiver, int index) throws ArrayIndexOutOfBoundsException {
589+
return index;
590+
}
591+
}
592+
593+
@GenerateLibrary
594+
@SuppressWarnings({"deprecation", "static-method"})
595+
public abstract static class ReplacementsLibraryErrors4 extends Library {
596+
597+
@Deprecated
598+
public int readUnsigned(Object receiver, int index) {
599+
throw new UnsupportedOperationException();
600+
}
601+
602+
@ExpectError("The replacement method readUnsignedLegacy does not have signature and thrown types equal to the message readUnsigned(Object, int) it replaces.")
603+
@Abstract(replacementOf = "readUnsigned(Object, int)", replacementMethod = "readUnsignedLegacy")
604+
public int readUnsigned(Object receiver, long index) {
605+
if (0 <= index && index <= 0xFFFFFFFFL) {
606+
return readUnsigned(receiver, (int) (0xFFFFFFFFL & index));
607+
}
608+
throw new UnsupportedOperationException();
609+
}
610+
611+
protected final long readUnsignedLegacy(Object receiver, int index) {
612+
return index;
613+
}
614+
}
615+
616+
@GenerateLibrary
617+
public abstract static class ReplacementsLibraryErrors5 extends Library {
618+
619+
@Abstract
620+
public boolean isType(Object receiver) {
621+
return false;
622+
}
623+
624+
@ExpectError("The 'replacementMethod' attribute is only valid when 'replacementOf' is also specified.")
625+
@Abstract(replacementMethod = "isType")
626+
public Object replacedErr(Object receiver) {
627+
return receiver;
628+
}
629+
630+
@ExpectError("The replacement method doReplaceNone does not exist.")
631+
@Abstract(replacementOf = "isType", replacementMethod = "doReplaceNone")
632+
public boolean replaceWithNonexisting(Object receiver) {
633+
return receiver != null;
634+
}
635+
636+
@ExpectError("Message replace2 is a replacement of multiple messages. Arguments to replacementOf annotation have to be unique.")
637+
@Abstract(replacementOf = "isType")
638+
public boolean replace2(Object receiver) {
639+
return receiver != null;
640+
}
641+
}
642+
305643
interface ExportsType {
306644
}
307645

truffle/src/com.oracle.truffle.api.library.test/src/com/oracle/truffle/api/library/test/LibraryAssertionsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public void testAssertion() throws Exception {
108108
}
109109

110110
ReflectionLibrary reflection = createCached(ReflectionLibrary.class, "");
111-
assertEquals(42, reflection.send("", Message.resolve(TestLibrary1.class, "foo"), 42));
111+
assertEquals(42, reflection.send("", Message.resolveExact(TestLibrary1.class, "foo", Object.class, int.class), 42));
112112
assertEquals(expectedCalls, fooCalls);
113113
}
114114

0 commit comments

Comments
 (0)