Skip to content

Commit

Permalink
https://github.com/manifold-systems/manifold/issues/396
Browse files Browse the repository at this point in the history
- Support case where Iterable is made structural via extension. As such, foreach stmt should work with types that implement Iterable structurally. Note, this did not work initially because javac foreach desugar happens during Gen (in Lower).
  • Loading branch information
rsmckinney committed Nov 17, 2022
1 parent 062982b commit 6983e7a
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -600,4 +596,10 @@ public static Object unFakeProxy( Object proxy )
}
return proxy;
}

@SuppressWarnings( "unused" )
public static <T> Iterable<T> makeIterable( Iterator<T> iter )
{
return () -> iter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ public void testStructural()
assertSame( itf, tf );
}

public void testStructuralIterator()
{
StringBuilder sb = new StringBuilder();
for( Character c : "hello" ) // String extension impls iterator()
{
sb.append( c );
}
assertEquals( "hello", sb.toString() );
}

public void testHashMap()
{
HashMap<String, String> map = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 - Manifold Systems LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package manifold.ext.extensions.java.lang.Iterable;

import manifold.ext.rt.api.Extension;
import manifold.ext.rt.api.Structural;

/** Make Iterable a structural interface to test Iterable in foreach stmt */
@Extension
@Structural
public class MyIterableExt
{
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package manifold.ext.extensions.java.lang.String;

import java.util.Iterator;
import java.util.List;
import manifold.ext.rt.api.Extension;
import manifold.ext.rt.api.This;

/**
*/
@Extension
public abstract class MyStringExt
public abstract class MyStringExt implements Iterable<Character>
{
public static void echo( @This String thiz )
{
Expand Down Expand Up @@ -52,4 +53,25 @@ public static String valueOf( List list )
}
return sb.toString();
}

/** Test Iterable as a structural interface in a foreach stmt */
@Extension
public static Iterator<Character> iterator( @This String thiz )
{
return new Iterator<Character>()
{
private int _index = -1;
@Override
public boolean hasNext()
{
return _index < thiz.length()-1;
}

@Override
public Character next()
{
return thiz.charAt( ++_index );
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,69 @@ private static boolean isJailbreakReceiver( JCTree.JCNewClass newExpr )
return false;
}

@Override
public void visitForeachLoop( JCTree.JCEnhancedForLoop tree )
{
super.visitForeachLoop( tree );

// Support case where Iterable is made structural via extension.
// In this case foreach should work with types that implement Iterable structurally.

Type iterableType = _tp.getTypes().erasure( _tp.getSymtab().iterableType );

if( TypeUtil.isStructuralInterface( _tp, iterableType.tsym ) &&
_tp.getTypes().isAssignable( tree.expr.type.tsym.type, iterableType ) )
{
// replace with: makeIterable( expr.iterator() )

Names names = Names.instance( _tp.getContext() );
Symbol.ClassSymbol runtimeMethodsClassSym = getRtClassSym( RuntimeMethods.class );

Symbol.MethodSymbol makeIterableMethod = resolveMethod( tree.expr.pos(), names.fromString( "makeIterable" ), runtimeMethodsClassSym.type,
List.from( new Type[]{_tp.getSymtab().iteratorType} ) );

TreeMaker make = _tp.getTreeMaker();
JavacElements javacElems = _tp.getElementUtil();

Symbol.MethodSymbol iteratorMethSym = resolveMethod( tree.expr.pos(), names.fromString( "iterator" ), tree.expr.type, List.nil() );
JCTree.JCFieldAccess iterMethAccess = make.Select( tree.expr, names.fromString( "iterator" ) );
iterMethAccess.type = getIteratorType( names, tree.expr.type );
iterMethAccess.sym = iteratorMethSym;
JCTree.JCMethodInvocation iteratorCall = make.Apply( List.nil(), iterMethAccess, List.nil() );
iteratorCall.setPos( tree.expr.pos );
iteratorCall.type = iterMethAccess.type;
((JCTree.JCFieldAccess)iteratorCall.meth).sym = iteratorMethSym;
iteratorCall = maybeReplaceWithExtensionMethod( iteratorCall );
iteratorCall = maybeReplaceWithStructuralCall( iteratorCall );

JCTree.JCMethodInvocation makeIterableCall = make.Apply( List.nil(),
memberAccess( make, javacElems, RuntimeMethods.class.getTypeName() + ".makeIterable" ), List.of( iteratorCall ) );
makeIterableCall.setPos( tree.expr.pos );
makeIterableCall.type = iterableType;
JCTree.JCFieldAccess methodSelect = (JCTree.JCFieldAccess)makeIterableCall.getMethodSelect();
methodSelect.sym = makeIterableMethod;
methodSelect.type = makeIterableMethod.type;
methodSelect.pos = tree.expr.pos;
assignTypes( methodSelect.selected, runtimeMethodsClassSym );
methodSelect.selected.pos = tree.expr.pos;
tree.expr = makeIterableCall;
}
}

private Type getIteratorType( Names names, Type type )
{
Iterable<Symbol> matches = IDynamicJdk.instance().getMembersByName( (Symbol.ClassSymbol)type.tsym, names.fromString( "iterator" ) );
for( Symbol s: matches )
{
if( s instanceof Symbol.MethodSymbol && ((Symbol.MethodSymbol)s).params().isEmpty() )
{
return _tp.getTypes().memberType( type, s ).getReturnType();
}
}
//## todo: should be a compile error?
return _tp.getTypes().erasure( _tp.getSymtab().iteratorType );
}

@Override
public void visitAnnotation( JCTree.JCAnnotation tree )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ private void genInterfaceMethodDecl( StringBuilder sb, Type iface, MethodSymbol
}
if( !mi.getReturnType().isPrimitive() )
{
sb.append( ", " ).append( returnType ).append( ".class);\n" );
sb.append( ", " ).append( mi.getReturnType().tsym.toString() ).append( ".class);\n" );
}
else
{
Expand Down

0 comments on commit 6983e7a

Please sign in to comment.