Search code examples
extension-methodsxtend

How can I configure the precedance of extension methods in name conflicts?


I would like to create an extension method named "map" to make inline mappings of single objects. For example, if you have some json datastructure:

val json = getDataStructure()
val String text = json.customers.findFirst[address.city=="Hamburg"]
                      .map['''I want to use «it.firstname» often without tmp-vars.''']
                      .split(" ").join("\n")

Unfortunately I have to decide if I want to use this (my) map extension method or if I want to use the ListExtensions.map method.

Is there a way to avoid this problem? I am also interested in a general answer to the problem of hidden extension methods / precedance of usage.


Solution

  • Unfortunately there doesn't seem any documentation about how precedence is generally handled or how to hide the implicitly imported xtend extensions.

    If the library method and your custom method have unambiguous signatures, it should work right if you use static extension imports. I've successfully tested this:

    package test1
    
    class CustomListExtensions {
    
        static def <T, R> map(T original, (T)=>R transformation) {
            return transformation.apply(original);
        }
    }
    

    usage example:

    package test2
    
    import static extension test1.CustomListExtensions.*
    
    class Test1 {
    
        def static void main(String[] args) {
    
            // uses org.eclipse.xtext.xbase.lib.ListExtensions.map
            val mappedList = #["hello", "world"].map['''«it» has length «length»''']
    
            // uses test1.CustomListExtensions.map
            val mappedObject = "hello CustomListExtensions".map['''«it» has length «length»''']
    
            mappedList.forEach[println(it)]
            println(mappedObject)
        }
    
    }
    

    Output:

    hello has length 5
    world has length 5
    hello CustomListExtensions has length 26
    

    If you use extension providers (with non-static methods) instead of static extensions imports, then it seems the extension provider has precedence over the library method:

    package test1

    class CustomListExtensions2 {
    
        def <T, R> map(T original, (T)=>R transformation) {
            return transformation.apply(original);
        }
    }
    

    Usage:

    package test2
    
    import test1.CustomListExtensions2
    
    class Test2 {
    
        val extension CustomListExtensions2 = new CustomListExtensions2
    
        def void main() {
    
            // uses test1.CustomListExtensions.map (<List<String>, String>)
            val mappedObject1 = #["hello", "world"].map['''«it» has length «length»''']
    
            // uses test1.CustomListExtensions.map (<String, String>)
            val mappedObject2 = "hello CustomListExtensions".map['''«it» has length «length»''']
    
            println(mappedObject1)
            println(mappedObject2)
        }
    
    }
    

    Output:

    [hello, world] has length 2
    hello CustomListExtensions has length 26