diff --git a/build.gradle b/build.gradle index 3023ef6..a8998c8 100644 --- a/build.gradle +++ b/build.gradle @@ -84,6 +84,7 @@ dependencies { implementation group: 'systems.manifold', name: 'manifold-ext', version: manifoldVersion implementation group: 'systems.manifold', name: 'manifold-props', version: manifoldVersion implementation group: 'systems.manifold', name: 'manifold-delegation', version: manifoldVersion + implementation group: 'systems.manifold', name: 'manifold-typealias', version: manifoldVersion implementation group: 'systems.manifold', name: 'manifold-strings', version: manifoldVersion implementation group: 'systems.manifold', name: 'manifold-exceptions', version: manifoldVersion implementation group: 'systems.manifold', name: 'manifold-preprocessor', version: manifoldVersion diff --git a/src/main/java/manifold/ij/core/ManModule.java b/src/main/java/manifold/ij/core/ManModule.java index ec995f5..6cc677b 100644 --- a/src/main/java/manifold/ij/core/ManModule.java +++ b/src/main/java/manifold/ij/core/ManModule.java @@ -74,6 +74,7 @@ public class ManModule extends SimpleModule private final LocklessLazyVar _isPreprocessorEnabled; private final LocklessLazyVar _isPropertiesEnabled; private final LocklessLazyVar _isDelegationEnabled; + private final LocklessLazyVar _isTypeAliasEnabled; private final LocklessLazyVar _isTuplesEnabled; ManModule( ManProject manProject, Module ijModule, List classpath, List sourcePath, List outputPath, List excludedDirs ) @@ -95,6 +96,7 @@ public class ManModule extends SimpleModule _isPreprocessorEnabled = LocklessLazyVar.make( () -> hasJar( "manifold-preprocessor" ) || hasJar( "manifold-all" ) ); _isPropertiesEnabled = LocklessLazyVar.make( () -> hasJar( "manifold-props" ) || hasJar( "manifold-all" ) ); _isDelegationEnabled = LocklessLazyVar.make( () -> hasJar( "manifold-delegation" ) || hasJar( "manifold-all" ) ); + _isTypeAliasEnabled = LocklessLazyVar.make( () -> hasJar( "manifold-typealias" ) || hasJar( "manifold-all" ) ); _isTuplesEnabled = LocklessLazyVar.make( () -> hasJar( "manifold-tuple" ) || hasJar( "manifold-all" ) ); } @@ -488,6 +490,11 @@ public boolean isDelegationEnabled() return _isDelegationEnabled.get(); } + public boolean isTypeAliasEnabled() + { + return _isTypeAliasEnabled.get(); + } + public boolean isTuplesEnabled() { return _isTuplesEnabled.get(); diff --git a/src/main/java/manifold/ij/core/ManProject.java b/src/main/java/manifold/ij/core/ManProject.java index 9ed1b29..48548e7 100644 --- a/src/main/java/manifold/ij/core/ManProject.java +++ b/src/main/java/manifold/ij/core/ManProject.java @@ -291,6 +291,30 @@ public boolean isDelegationEnabledInAnyModules() } return modules.values().stream().anyMatch( m -> m.isDelegationEnabled() ); } + public static boolean isTypeAliasEnabledInAnyModules( PsiElement element ) + { + ManProject manProject = ManProject.manProjectFrom( element.getProject() ); + if( manProject == null ) + { + return false; + } + return manProject.isTypeAliasEnabledInAnyModules(); + } + + public boolean isTypeAliasEnabledInAnyModules() + { + if( !isManifoldInUse() ) + { + return false; + } + + Map modules = getModules(); + if( modules == null ) + { + return false; + } + return modules.values().stream().anyMatch( m -> m.isTypeAliasEnabled() ); + } public boolean isPreprocessorEnabledInAnyModules() { diff --git a/src/main/java/manifold/ij/extensions/ManJavaClassSupersImpl.java b/src/main/java/manifold/ij/extensions/ManJavaClassSupersImpl.java new file mode 100644 index 0000000..e0da2e7 --- /dev/null +++ b/src/main/java/manifold/ij/extensions/ManJavaClassSupersImpl.java @@ -0,0 +1,117 @@ +package manifold.ij.extensions; + +import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiSubstitutor; +import com.intellij.psi.SmartPointerManager; +import com.intellij.psi.SmartPsiElementPointer; +import com.intellij.psi.impl.JavaClassSupersImpl; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.CachedValue; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.JavaClassSupers; +import manifold.ij.core.ManProject; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public class ManJavaClassSupersImpl extends JavaClassSupers +{ + private static final Key>> KEY_CACHED_TYPE_ALIAS_SUBSTITUTES = new Key<>("KEY_CACHED_TYPE_ALIAS_SUBSTITUTES"); + private final JavaClassSupersImpl impl = new JavaClassSupersImpl(); + + @Nullable + @Override + public PsiSubstitutor getSuperClassSubstitutor( @NotNull PsiClass superClass, @NotNull PsiClass derivedClass, @NotNull GlobalSearchScope resolveScope, @NotNull PsiSubstitutor derivedSubstitutor ) + { + // for performance reasons, check only if non-inherited. + PsiSubstitutor substitutor = impl.getSuperClassSubstitutor( superClass, derivedClass, resolveScope, derivedSubstitutor ); + if( substitutor == null ) + { + substitutor = getAliasedClassSubstitutor( superClass, derivedClass ); + } + return substitutor; + } + + @Override + public void reportHierarchyInconsistency( @NotNull PsiClass superClass, @NotNull PsiClass derivedClass ) + { + impl.reportHierarchyInconsistency( superClass, derivedClass ); + } + + private PsiSubstitutor getAliasedClassSubstitutor( @NotNull PsiClass superClass, @NotNull PsiClass derivedClass ) + { + if( !ManProject.isTypeAliasEnabledInAnyModules( derivedClass ) ) + { + // Manifold jars are not used in the project + return null; + } + // AliasedType = OriginType + PsiSubstitutor substitutor = getAliasedClassSubstitutor0( superClass, derivedClass ); + if( substitutor != null ) + { + return substitutor; + } + // OriginType = AliasedType + return getAliasedClassSubstitutor0( derivedClass, superClass ); + } + + private PsiSubstitutor getAliasedClassSubstitutor0( @NotNull PsiClass superClass, @NotNull PsiClass derivedClass ) + { + return CachedValuesManager.getCachedValue( superClass, KEY_CACHED_TYPE_ALIAS_SUBSTITUTES, new MyCachedValueProvider( superClass ) ).get( derivedClass.getQualifiedName() ); + } + + private static class MyCachedValueProvider implements CachedValueProvider> + { + private final SmartPsiElementPointer _psiClassPointer; + + public MyCachedValueProvider( PsiClass psiClass ) + { + _psiClassPointer = SmartPointerManager.createPointer( psiClass ); + } + + @Nullable + @Override + public Result> compute() + { + HashMap substitutes = new HashMap<>(); + PsiClass psiClass = _psiClassPointer.getElement(); + if( psiClass == null ) + { + return Result.create( substitutes ); + } + Set dependencies = new LinkedHashSet<>(); + PsiClass newPsiClass = TypeAliasMaker.getAliasedType( psiClass ); + if( newPsiClass != null ) + { + substitutes.put( newPsiClass.getQualifiedName(), PsiSubstitutor.EMPTY ); + dependencies.add( TypeAliasMaker.getAnnotation( psiClass ) ); + } + dependencies.add( psiClass ); + return Result.create( substitutes, dependencies.toArray() ); + } + + @Override + public int hashCode() + { + return Objects.hashCode( _psiClassPointer.getElement() ); + } + + @Override + public boolean equals( Object obj ) + { + if( obj instanceof MyCachedValueProvider other) + { + return Objects.equals( other._psiClassPointer.getElement(), _psiClassPointer.getElement() ); + } + return false; + } + } +} diff --git a/src/main/java/manifold/ij/extensions/ManMethodSuperSearcher.java b/src/main/java/manifold/ij/extensions/ManMethodSuperSearcher.java new file mode 100644 index 0000000..af13e1f --- /dev/null +++ b/src/main/java/manifold/ij/extensions/ManMethodSuperSearcher.java @@ -0,0 +1,111 @@ +package manifold.ij.extensions; + +import com.intellij.openapi.application.QueryExecutorBase; +import com.intellij.psi.HierarchicalMethodSignature; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiSubstitutor; +import com.intellij.psi.PsiType; +import com.intellij.psi.PsiTypeParameter; +import com.intellij.psi.search.searches.SuperMethodsSearch; +import com.intellij.psi.util.MethodSignature; +import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; +import com.intellij.psi.util.MethodSignatureUtil; +import com.intellij.util.Processor; +import manifold.ij.core.ManProject; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ManMethodSuperSearcher extends QueryExecutorBase +{ + @Override + public void processQuery( @NotNull SuperMethodsSearch.SearchParameters queryParameters, @NotNull Processor consumer ) + { + PsiMethod method = queryParameters.getMethod(); + if( !ManProject.isTypeAliasEnabledInAnyModules( method ) ) + { + return; + } + HierarchicalMethodSignature signature = method.getHierarchicalMethodSignature(); + List supers = signature.getSuperSignatures(); + if( !supers.isEmpty() ) + { + return; + } + for( HierarchicalMethodSignature superSignature : getSameNameSuperSignatures( method, signature ) ) + { + if( MethodSignatureUtil.isSubsignature( superSignature, resolve( superSignature, signature ) ) ) + { + consumer.process( superSignature ); + } + } + } + + private static MethodSignature resolve( @NotNull MethodSignature superSignature, @NotNull MethodSignature subSignature ) + { + // resolve parameter type + PsiType[] oldParameterTypes = subSignature.getParameterTypes(); + PsiType[] superParameterTypes = superSignature.getParameterTypes(); + PsiType[] newParameterTypes = oldParameterTypes; + for( int i = 0; i < newParameterTypes.length; ++i ) + { + if( newParameterTypes[i].isAssignableFrom(superParameterTypes[i]) ) + { + if( newParameterTypes == oldParameterTypes ) + { + newParameterTypes = oldParameterTypes.clone(); + } + newParameterTypes[i] = superParameterTypes[i]; + } + } + // resolve type parameter. + PsiTypeParameter[] oldTypeParameterList = subSignature.getTypeParameters(); + PsiTypeParameter[] superTypeParameterList = superSignature.getTypeParameters(); + PsiTypeParameter[] newTypeParameterList = oldTypeParameterList; + for( int i = 0; i < newTypeParameterList.length; ++i ) + { + if( newTypeParameterList[i].isInheritor( superTypeParameterList[i], false ) ) + { + if( newTypeParameterList == oldTypeParameterList ) + { + newTypeParameterList = oldTypeParameterList.clone(); + } + newTypeParameterList[i] = superTypeParameterList[i]; + } + } + if( newParameterTypes == oldParameterTypes && newTypeParameterList == oldTypeParameterList ) + { + return subSignature; + } + String name = subSignature.getName(); + PsiSubstitutor substitutor = subSignature.getSubstitutor(); + boolean isConstructor = subSignature.isConstructor(); + return MethodSignatureUtil.createMethodSignature( name, newParameterTypes, newTypeParameterList, substitutor, isConstructor ); + } + + private static List getSameNameSuperSignatures( PsiMethod method, HierarchicalMethodSignature signature ) + { + if( !(method.getParent() instanceof PsiClass psiClass) ) + { + return Collections.emptyList(); + } + ArrayList methodSignatures = new ArrayList<>(); + int parameterCount = signature.getParameterTypes().length; + int typeParameterCount = signature.getTypeParameters().length; + for( PsiClass psiSuperClass : psiClass.getSupers() ) + { + for( PsiMethod psiSuperMethod : psiSuperClass.findMethodsByName( method.getName(), true ) ) + { + HierarchicalMethodSignature superSignature = psiSuperMethod.getHierarchicalMethodSignature(); + if( parameterCount == superSignature.getParameterTypes().length && typeParameterCount == superSignature.getTypeParameters().length ) + { + methodSignatures.add( superSignature ); + } + } + } + return methodSignatures; + } +} diff --git a/src/main/java/manifold/ij/extensions/TypeAliasAugmentProvider.java b/src/main/java/manifold/ij/extensions/TypeAliasAugmentProvider.java new file mode 100644 index 0000000..c20e2f7 --- /dev/null +++ b/src/main/java/manifold/ij/extensions/TypeAliasAugmentProvider.java @@ -0,0 +1,186 @@ +/* + * + * * 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.ij.extensions; + +import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMember; +import com.intellij.psi.SmartPointerManager; +import com.intellij.psi.SmartPsiElementPointer; +import com.intellij.psi.augment.PsiAugmentProvider; +import com.intellij.psi.impl.source.PsiExtensibleClass; +import com.intellij.psi.util.CachedValue; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import manifold.ij.core.ManProject; +import manifold.util.ReflectUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * - Generate stubbed methods for type aliased class + */ +public class TypeAliasAugmentProvider extends PsiAugmentProvider { + + static final Key>> KEY_CACHED_TYPE_ALIAS_AUGMENTS = new Key<>("KEY_CACHED_TYPE_ALIAS_AUGMENTS"); + + @SuppressWarnings("deprecation") + @NotNull + public List getAugments( @NotNull PsiElement element, @NotNull Class cls ) + { + return getAugments( element, cls, null ); + } + + @NotNull + public List getAugments( @NotNull PsiElement element, @NotNull Class cls, String nameHint ) + { + return ApplicationManager.getApplication().runReadAction( (Computable>) () -> _getAugments( element, cls ) ); + } + + private List _getAugments( PsiElement element, Class cls ) + { + // first search all element in the element. + Collection allElements = _getExtendedMembers( element ); + if( allElements.isEmpty() ) + { + return Collections.emptyList(); + } + // filter specified element and the cast to target class. + ArrayList results = new ArrayList<>( allElements.size() ); + for( PsiElement psiMemberElement : allElements ) + { + if( cls.isInstance( psiMemberElement ) ) + { + results.add( cls.cast( psiMemberElement ) ); + } + } + return results; + } + + private Collection _getExtendedMembers(PsiElement element ) + { + // Module is assigned to user-data via ManTypeFinder, which loads the psiClass (element) + if( DumbService.getInstance( element.getProject() ).isDumb() ) + { + // skip processing during index rebuild + return Collections.emptyList(); + } + + if( !(element instanceof PsiExtensibleClass) || !element.isValid() ) + { + return Collections.emptyList(); + } + + if( !ManProject.isTypeAliasEnabledInAnyModules( element ) ) + { + // Manifold jars are not used in the project + return Collections.emptyList(); + } + + PsiExtensibleClass psiClass = (PsiExtensibleClass) element; + + if( psiClass.getLanguage() != JavaLanguage.INSTANCE && + psiClass.getLanguage().getBaseLanguage() != JavaLanguage.INSTANCE ) + { + return Collections.emptyList(); + } + + PsiClass newPsiClass = TypeAliasMaker.getAliasedType( psiClass ); + if( newPsiClass == null ) + { + return Collections.emptyList(); + } +// System.out.println("get all from: " + psiClass + "(" + newPsiClass + ")" ); + +// Not cached: +// LinkedHashSet augFeatures = new LinkedHashSet<>(); +// +// if( PsiMethod.class.isAssignableFrom( cls ) ) +// { +// addMethods( psiClass, augFeatures ); +// } +// +// //noinspection unchecked +// return new ArrayList<>( (Collection)augFeatures ); + +// Cached: + ReflectUtil.FieldRef DO_CHECKS = ReflectUtil.field("com.intellij.util.CachedValueStabilityChecker", "DO_CHECKS"); + try { if ((boolean) DO_CHECKS.getStatic()) { DO_CHECKS.setStatic(false); } } catch (Throwable ignore) { } + return CachedValuesManager.getCachedValue( psiClass, KEY_CACHED_TYPE_ALIAS_AUGMENTS, new MyCachedValueProvider( psiClass ) ); + } + + private static class MyCachedValueProvider implements CachedValueProvider> + { + private final SmartPsiElementPointer _psiClassPointer; + + public MyCachedValueProvider( PsiExtensibleClass psiClass ) + { + _psiClassPointer = SmartPointerManager.createPointer( psiClass ); + } + + @Nullable + @Override + public Result> compute() + { + PsiExtensibleClass psiClass = _psiClassPointer.getElement(); + PsiClass newPsiClass = TypeAliasMaker.getAliasedType( psiClass ); + if( psiClass == null || newPsiClass == null ) + { + return Result.create( Collections.emptyList() ); + } + LinkedHashSet features = new LinkedHashSet<>(); + Set dependencies = new LinkedHashSet<>(); + TypeAliasMaker.generateMembers( psiClass, newPsiClass, features ); + dependencies.add( psiClass ); + dependencies.add( newPsiClass ); + dependencies.add( TypeAliasMaker.getAnnotation( psiClass ) ); + return Result.create( features, dependencies.toArray() ); + } + + @Override + public int hashCode() + { + return Objects.hash( _psiClassPointer.getElement() ); + } + + @Override + public boolean equals( Object obj ) + { + if( obj instanceof MyCachedValueProvider other ) + { + return Objects.equals( _psiClassPointer.getElement(), other._psiClassPointer.getElement() ); + } + return false; + } + } +} diff --git a/src/main/java/manifold/ij/extensions/TypeAliasClassAnnotator.java b/src/main/java/manifold/ij/extensions/TypeAliasClassAnnotator.java new file mode 100644 index 0000000..098ab56 --- /dev/null +++ b/src/main/java/manifold/ij/extensions/TypeAliasClassAnnotator.java @@ -0,0 +1,483 @@ +/* + * + * * 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.ij.extensions; + +import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.lang.annotation.Annotator; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.FileIndexUtil; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiClassType; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiJavaFile; +import com.intellij.psi.PsiKeyword; +import com.intellij.psi.PsiManager; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiModifierList; +import com.intellij.psi.PsiPackageStatement; +import com.intellij.psi.PsiParameter; +import com.intellij.psi.PsiPrimitiveType; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiType; +import com.intellij.psi.impl.source.HierarchicalMethodSignatureImpl; +import com.intellij.psi.impl.source.PsiClassImpl; +import com.intellij.psi.impl.source.PsiExtensibleClass; +import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl; +import com.intellij.psi.impl.source.PsiJavaFileImpl; +import com.intellij.psi.impl.source.PsiMethodImpl; +import com.intellij.psi.impl.source.tree.java.ReferenceListElement; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.ClassUtil; +import com.intellij.psi.util.MethodSignatureUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiTypesUtil; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.util.SlowOperations; +import manifold.ExtIssueMsg; +import manifold.api.fs.IFile; +import manifold.api.type.ITypeManifold; +import manifold.ext.ExtensionManifold; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.Structural; +import manifold.ext.rt.api.This; +import manifold.ext.rt.api.ThisClass; +import manifold.ext.typealias.rt.api.TypeAlias; +import manifold.ij.core.ManModule; +import manifold.ij.core.ManProject; +import manifold.ij.fs.IjFile; +import manifold.rt.api.Array; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Modifier; +import java.util.List; + +import static manifold.api.type.ContributorKind.Supplemental; + +public class TypeAliasClassAnnotator implements Annotator +{ + @Override + public void annotate( @NotNull PsiElement element, @NotNull AnnotationHolder holder ) + { + if( DumbService.getInstance( element.getProject() ).isDumb() ) + { + // skip processing during index rebuild + return; + } + +// PsiClass psiAliasingClass = findTypeAliasClass( element ); +// if( psiAliasingClass != null ) +// { +// verifyPackage( element, holder ); +//// verifyCanExtend( element, holder ); +//// verifyExtensionInterfaces( element, holder ); +//// verifyExtensionMethods( element, holder ); +// } + } + + private void verifyExtensionMethods( PsiElement element, AnnotationHolder holder ) + { + if( !(element instanceof PsiMethodImpl) ) + { + return; + } + + PsiMethodImpl psiMethod = (PsiMethodImpl)element; + + String extendedClassName = getExtendedClassName( ((PsiJavaFile)psiMethod.getContainingFile()).getPackageName() ); + + boolean thisAnnoFound = false; + final long modifiers = StubBuilder.getModifiers( psiMethod.getModifierList() ); + PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); + for( int i = 0; i < parameters.length; i++ ) + { + PsiParameter param = parameters[i]; + PsiModifierList modifierList = param.getModifierList(); + boolean This; + if( modifierList != null && + ((This = (modifierList.findAnnotation( This.class.getName() ) != null)) || + modifierList.findAnnotation( ThisClass.class.getName() ) != null) ) + { + thisAnnoFound = true; + + if( i != 0 ) + { + // @This and @ThisClass must be on first parameter + + TextRange range = new TextRange( param.getTextRange().getStartOffset(), + param.getTextRange().getEndOffset() ); + holder.newAnnotation( HighlightSeverity.ERROR, ExtIssueMsg.MSG_THIS_FIRST.get() ) + .range( range ) + .create(); + } + + if( param.getType() instanceof PsiPrimitiveType || !getRawTypeName( param ).equals( extendedClassName ) ) + { + PsiClass extendClassSym = JavaPsiFacade.getInstance( element.getProject() ) + .findClass( extendedClassName, GlobalSearchScope.allScope( element.getProject() ) ); + if( This && extendedClassName.equals( Array.class.getTypeName() ) ) + { + // @This must have Object type for array extension + + if( !(param.getType() instanceof PsiClassType) || !((PsiClassType)param.getType()).getName().equals( Object.class.getSimpleName() ) ) + { + TextRange range = new TextRange( param.getTextRange().getStartOffset(), + param.getTextRange().getEndOffset() ); + holder.newAnnotation( HighlightSeverity.ERROR, ExtIssueMsg.MSG_EXPECTING_OBJECT_FOR_THIS.get() ) + .range( range ) + .create(); + } + } + else if( This && extendClassSym != null && + !isStructuralInterface( extendClassSym ) && // an extended class could be made a structural interface which results in Object as @This param, ignore this + !isAssignableFromRaw( element.getProject(), extendClassSym, param.getType() ) && + (ManifoldPsiClassAnnotator.getContainingClass( extendClassSym ) == null || !isEnclosing( extendClassSym, param )) ) // handle inner class extensions + { + // @This must have enclosing class type + + TextRange range = new TextRange( param.getTextRange().getStartOffset(), + param.getTextRange().getEndOffset() ); + holder.newAnnotation( HighlightSeverity.ERROR, ExtIssueMsg.MSG_EXPECTING_TYPE_FOR_THIS.get( extendedClassName ) ) + .range( range ) + .create(); + } + else if( !This && extendClassSym != null ) + { + // @ThisClass must have Class type + + boolean valid = param.getType() instanceof PsiClassType; + if( valid ) + { + PsiClass psiClass = PsiTypesUtil.getPsiClass( param.getType() ); + valid = psiClass != null && Class.class.getName().equals( psiClass.getQualifiedName() ); + } + if( !valid ) + { + TextRange range = new TextRange( param.getTextRange().getStartOffset(), + param.getTextRange().getEndOffset() ); + holder.newAnnotation( HighlightSeverity.ERROR, ExtIssueMsg.MSG_EXPECTING_CLASS_TYPE_FOR_THISCLASS.get( extendedClassName ) ) + .range( range ) + .create(); + } + } + } + } + else if( i == 0 && + Modifier.isStatic( (int)modifiers ) && + Modifier.isPublic( (int)modifiers ) && + getRawTypeName( param ).equals( extendedClassName ) ) + { + // Warn if it looks like @This is missing + + TextRange range = new TextRange( param.getTextRange().getStartOffset(), + param.getTextRange().getEndOffset() ); + holder.newAnnotation( HighlightSeverity.WARNING, ExtIssueMsg.MSG_MAYBE_MISSING_THIS.get() ) + .range( range ) + .create(); + } + } + + if( thisAnnoFound || psiMethod.getModifierList().findAnnotation( Extension.class.getName() ) != null ) + { + if( !Modifier.isStatic( (int)modifiers ) ) + { + TextRange range = new TextRange( psiMethod.getNavigationElement().getTextRange().getStartOffset(), + psiMethod.getNavigationElement().getTextRange().getEndOffset() ); + holder.newAnnotation( HighlightSeverity.WARNING, ExtIssueMsg.MSG_MUST_BE_STATIC.get( psiMethod.getName() ) ) + .range( range ) + .create(); + } + + if( Modifier.isPrivate( (int)modifiers ) ) + { + TextRange range = new TextRange( psiMethod.getNavigationElement().getTextRange().getStartOffset(), + psiMethod.getNavigationElement().getTextRange().getEndOffset() ); + holder.newAnnotation( HighlightSeverity.WARNING, ExtIssueMsg.MSG_MUST_NOT_BE_PRIVATE.get( psiMethod.getName() ) ) + .range( range ) + .create(); + } + } + } + + private boolean isEnclosing( PsiClass topLevelExtendedClass, PsiParameter thisParam ) + { + PsiClass paramClass = PsiTypesUtil.getPsiClass( thisParam.getType() ); + return paramClass != null && + (PsiTreeUtil.isAncestor( topLevelExtendedClass, paramClass, true ) || + topLevelExtendedClass instanceof ManifoldPsiClass && PsiTreeUtil.isAncestor( + ((ManifoldPsiClass)topLevelExtendedClass).getDelegate(), paramClass, true )); + } + + private boolean isAssignableFromRaw( Project project, PsiClass extendedClassSym, PsiType thisParamType ) + { + PsiType extendedType = JavaPsiFacade.getInstance( project ).getElementFactory().createType( extendedClassSym ); + extendedType = TypeConversionUtil.erasure( extendedType ); + return TypeConversionUtil.erasure( thisParamType ).isAssignableFrom( extendedType ); + } + + @NotNull + private String getRawTypeName( PsiParameter param ) + { + PsiType type = param.getType(); + if( type instanceof PsiClassType ) + { + type = ((PsiClassType)type).rawType(); + } + return type.getCanonicalText(); + } + + private void verifyExtensionInterfaces( PsiElement element, AnnotationHolder holder ) + { + if( element instanceof PsiJavaCodeReferenceElementImpl && + ((PsiJavaCodeReferenceElementImpl)element).getTreeParent() instanceof ReferenceListElement && + ((PsiJavaCodeReferenceElementImpl)element).getTreeParent().getText().startsWith( PsiKeyword.IMPLEMENTS ) ) + { + PsiReference reference = element.getReference(); + if( reference == null ) + { + return; + } + final PsiElement resolve = reference.resolve(); + if( resolve instanceof PsiExtensibleClass ) + { + PsiExtensibleClass iface = (PsiExtensibleClass)resolve; + if( !isStructuralInterface( iface ) ) + { + TextRange range = new TextRange( element.getTextRange().getStartOffset(), + element.getTextRange().getEndOffset() ); + holder.newAnnotation( HighlightSeverity.ERROR, ExtIssueMsg.MSG_ONLY_STRUCTURAL_INTERFACE_ALLOWED_HERE.get( iface.getName() ) ) + .range( range ) + .create(); + } + } + } + } + + public static boolean isStructuralInterface( PsiClass iface ) + { + return SlowOperations.allowSlowOperations( () -> { + PsiModifierList modifierList = iface == null ? null : iface.getModifierList(); + return modifierList != null && + (modifierList.findAnnotation( Structural.class.getName() ) != null || + isInterfaceMadeStructuralByExtension( iface )); + } ); + } + + private void verifyPackage( PsiElement element, AnnotationHolder holder ) + { + if( !(element instanceof PsiPackageStatement) ) + { + return; + } + + String packageName = ((PsiPackageStatement)element).getPackageName(); + int iExt = packageName.indexOf( ExtensionManifold.EXTENSIONS_PACKAGE + '.' ); + if( iExt < 0 ) + { + TextRange range = new TextRange( element.getTextRange().getStartOffset(), + element.getTextRange().getEndOffset() ); + holder.newAnnotation( HighlightSeverity.ERROR, ExtIssueMsg.MSG_EXPECTING_EXTENSIONS_ROOT_PACKAGE.get( getPackageRoot( packageName ) ) ) + .range( range ) + .create(); + } + else + { + String extendedClassName = getExtendedClassName( packageName ); + if( !extendedClassName.isEmpty() && + JavaPsiFacade.getInstance( element.getProject() ) + .findClass( extendedClassName, GlobalSearchScope.allScope( element.getProject() ) ) == null ) + { + TextRange range = new TextRange( element.getTextRange().getStartOffset(), + element.getTextRange().getEndOffset() ); + holder.newAnnotation( HighlightSeverity.ERROR, ExtIssueMsg.MSG_EXPECTING_EXTENDED_CLASS_NAME.get( getPackageRoot( extendedClassName ) ) ) + .range( range ) + .create(); + } + } + } + + private void verifyCanExtend( PsiElement element, AnnotationHolder holder ) + { + if( !(element instanceof PsiAnnotation) ) + { + return; + } + + String fqnAnnotation = ((PsiAnnotation)element).getQualifiedName(); + if( fqnAnnotation == null || !fqnAnnotation.equals( Extension.class.getTypeName() ) ) + { + return; + } + + PsiJavaFile psiFile = (PsiJavaFile)element.getContainingFile(); + String packageName = psiFile.getPackageName(); + int iExt = packageName.indexOf( ExtensionManifold.EXTENSIONS_PACKAGE + '.' ); + if( iExt >= 0 ) + { + String extendedClassName = getExtendedClassName( packageName ); + Project project = element.getProject(); + PsiClass psiExtended = JavaPsiFacade.getInstance( project ).findClass( extendedClassName, GlobalSearchScope.projectScope( project ) ); + if( psiExtended != null && + FileIndexUtil.isJavaSourceFile( project, psiExtended.getContainingFile().getVirtualFile() ) && + ManProject.getIjModule( psiExtended ) == ManProject.getIjModule( psiFile ) ) + { + TextRange range = new TextRange( element.getTextRange().getStartOffset(), + element.getTextRange().getEndOffset() ); + holder.newAnnotation( HighlightSeverity.WARNING, + ExtIssueMsg.MSG_CANNOT_EXTEND_SOURCE_FILE.get( extendedClassName ) ) + .range( range ) + .create(); + } + } + } + + private String getPackageRoot( String packageName ) + { + int iDot = packageName.indexOf( '.' ); + if( iDot < 0 ) + { + return packageName; + } + return packageName.substring( 0, iDot ); + } + + public static String getExtendedClassName( String packageName ) + { + int iExt = packageName.indexOf( ExtensionManifold.EXTENSIONS_PACKAGE + '.' ); + return packageName.substring( iExt + ExtensionManifold.EXTENSIONS_PACKAGE.length() + 1 ); + } + + private static boolean isInterfaceMadeStructuralByExtension( PsiClass psiExtentionInterface ) + { + Module module = ManProject.getIjModule( psiExtentionInterface ); + if( module != null ) + { + ManModule manModule = ManProject.getModule( module ); + return manModule != null && isInterfaceMadeStructuralByExtension( psiExtentionInterface, manModule ); + } + else + { + ManProject manProject = ManProject.manProjectFrom( psiExtentionInterface.getProject() ); + for( ManModule manModule : manProject.getModules().values() ) + { + if( isInterfaceMadeStructuralByExtension( psiExtentionInterface, manModule ) ) + { + return true; + } + } + } + return false; + } + + private static boolean isInterfaceMadeStructuralByExtension( PsiClass psiInterface, ManModule module ) + { + final String fqn = psiInterface.getQualifiedName(); + ManModule manModule = ManProject.getModule( module.getIjModule() ); + if( manModule == null ) + { + return false; + } + for( ITypeManifold sp : manModule.getTypeManifolds() ) + { + if( sp.getContributorKind() == Supplemental ) + { + if( sp.isType( fqn ) ) + { + List files = sp.findFilesForType( fqn ); + for( IFile file : files ) + { + VirtualFile vExtensionClassFile = ((IjFile)file.getPhysicalFile()).getVirtualFile(); + if( !vExtensionClassFile.isValid() ) + { + continue; + } + + PsiJavaFile psiExtClassJavaFile = + (PsiJavaFile)PsiManager.getInstance( module.getIjModule().getProject() ).findFile( vExtensionClassFile ); + PsiClass[] classes = new PsiClass[0]; + if( psiExtClassJavaFile != null ) + { + classes = psiExtClassJavaFile.getClasses(); + } + if( classes.length > 0 ) + { + PsiClass psiExtClass = classes[0]; + PsiModifierList modifierList = psiExtClass.getModifierList(); + if( modifierList != null && modifierList.findAnnotation( Structural.class.getName() ) != null ) + { + return true; + } + } + } + } + } + } + return false; + } + + public static PsiClass findTypeAliasClass( PsiElement element ) + { +// PsiFile containingFile = element.getContainingFile(); +// if( !(containingFile instanceof PsiJavaFileImpl) ) +// { +// return null; +// } +// +// PsiJavaFileImpl file = (PsiJavaFileImpl)containingFile; +// for( PsiClass psiClass : file.getClasses() ) +// { +// PsiModifierList modifierList = psiClass.getModifierList(); +// if( modifierList != null && modifierList.findAnnotation( TypeAlias.class.getName() ) != null ) +// { +// return psiClass; +// } +// } + + return null; + } + + private void errrantThisOrExtension( PsiElement element, AnnotationHolder holder ) + { + if( element instanceof PsiModifierList ) + { + PsiModifierList mods = (PsiModifierList)element; + PsiAnnotation annotation; + if( (annotation = mods.findAnnotation( Extension.class.getName() )) != null || + (annotation = mods.findAnnotation( This.class.getName() )) != null || + (annotation = mods.findAnnotation( ThisClass.class.getName() )) != null) + { + TextRange range = new TextRange( annotation.getTextRange().getStartOffset(), + annotation.getTextRange().getEndOffset() ); + //noinspection ConstantConditions + holder.newAnnotation( HighlightSeverity.ERROR, ExtIssueMsg.MSG_NOT_IN_EXTENSION_CLASS.get( ClassUtil.extractClassName( annotation.getQualifiedName() ) ) ) + .range( range ) + .create(); + } + } + } +} diff --git a/src/main/java/manifold/ij/extensions/TypeAliasExternalAnnotator.java b/src/main/java/manifold/ij/extensions/TypeAliasExternalAnnotator.java new file mode 100644 index 0000000..510c9e7 --- /dev/null +++ b/src/main/java/manifold/ij/extensions/TypeAliasExternalAnnotator.java @@ -0,0 +1,162 @@ +/* + * + * * 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.ij.extensions; + +import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.lang.annotation.ExternalAnnotator; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiJavaFile; +import com.intellij.psi.impl.source.PsiExtensibleClass; +import manifold.ij.core.ManModule; +import manifold.ij.core.ManProject; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class TypeAliasExternalAnnotator extends ExternalAnnotator +{ + @Nullable + @Override + public PsiFile collectInformation( @NotNull PsiFile file ) + { + return file; + } + + @Nullable + @Override + public PsiFile collectInformation( @NotNull PsiFile file, @NotNull Editor editor, boolean hasErrors ) + { + return file; + } + + @Nullable + @Override + public Info doAnnotate( PsiFile file ) + { + if( !(file instanceof PsiJavaFile) ) + { + return Info.EMPTY; + } + + return ApplicationManager.getApplication().runReadAction( (Computable)() -> { + PsiClass[] classes = ((PsiJavaFile)file).getClasses(); + if( classes.length == 0 ) + { + return Info.EMPTY; + } + + PsiClass psiClass = classes[0]; + if( !(psiClass instanceof PsiExtensibleClass) ) + { + return Info.EMPTY; + } + + if( !ManProject.isManifoldInUse( file ) ) + { + // Manifold jars are not used in the project + return Info.EMPTY; + } + + ManModule module = ManProject.getModule( file ); + if( module == null || !module.isTypeAliasEnabled() ) + { + return Info.EMPTY; + } + + Info info = new Info(); + annotate( (PsiExtensibleClass)psiClass, info ); + return info; + } ); + } + + private static void annotate( PsiExtensibleClass psiClass, Info info ) + { +// TypeAliasMaker.checkTypeAlias( psiClass, info ); +// +// for( PsiClass innerClass: psiClass.getInnerClasses() ) +// { +// if( innerClass instanceof PsiExtensibleClass ) +// { +// annotate( (PsiExtensibleClass)innerClass, info ); +// } +// } + } + + @Override + public void apply( @NotNull PsiFile file, Info info, @NotNull AnnotationHolder holder ) + { + if( !ManProject.isManifoldInUse( file ) ) + { + // Manifold jars are not used in the project + return; + } + + ManModule module = ManProject.getModule( file ); + if( module == null || !module.isTypeAliasEnabled() ) + { + // type alias not in use in module + return; + } + + info.getIssues().forEach( issue -> + holder.newAnnotation( issue._severity, issue._msg ) + .range( issue._range ) + .create() ); + } + + public static class Info + { + public static final Info EMPTY = new Info(); + + private final List _issues = new ArrayList<>(); + + public List getIssues() + { + return _issues; + } + + public void addIssue( HighlightSeverity severity, String msg, TextRange range ) + { + _issues.add( new Issue( severity, msg, range ) ); + } + + static class Issue + { + HighlightSeverity _severity; + String _msg; + TextRange _range; + + public Issue( HighlightSeverity severity, String msg, TextRange range ) + { + _severity = severity; + _msg = msg; + _range = range; + } + } + } +} diff --git a/src/main/java/manifold/ij/extensions/TypeAliasMaker.java b/src/main/java/manifold/ij/extensions/TypeAliasMaker.java new file mode 100644 index 0000000..eb1bb88 --- /dev/null +++ b/src/main/java/manifold/ij/extensions/TypeAliasMaker.java @@ -0,0 +1,236 @@ +/* + * + * * 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.ij.extensions; + +import com.intellij.codeInsight.generation.GenerateMembersUtil; +import com.intellij.lang.jvm.annotation.JvmAnnotationArrayValue; +import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute; +import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue; +import com.intellij.lang.jvm.annotation.JvmAnnotationClassValue; +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiClassType; +import com.intellij.psi.PsiMember; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiModifier; +import com.intellij.psi.PsiParameter; +import com.intellij.psi.PsiSubstitutor; +import com.intellij.psi.PsiTypeParameter; +import com.intellij.psi.impl.source.PsiExtensibleClass; +import manifold.ext.typealias.rt.api.TypeAlias; +import manifold.ij.core.ManModule; +import manifold.ij.core.ManProject; +import manifold.ij.psi.ManLightMethodBuilder; +import manifold.ij.psi.ManPsiElementFactory; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +public class TypeAliasMaker +{ + private static final ThreadLocal> _reenter = ThreadLocal.withInitial( () -> new HashSet<>() ); + + private final boolean _isAbstractClass; + + private final PsiClass _psiClass; + private final PsiClass _newPsiClass; + + private final LinkedHashSet _features; + + static void checkTypeAlias( PsiExtensibleClass psiClass, Object issueInfo ) + { +// new TypeAliasMaker( psiClass, issueInfo ).generateOrCheck(); + } + + static void generateMembers(PsiClass psiClass, PsiClass newPsiClass, LinkedHashSet features ) + { + String qname = psiClass.getQualifiedName(); + if( qname == null ) + { + return; + } + + if( _reenter.get().contains( qname ) ) + { + return; + } + _reenter.get().add( qname ); + try + { + new TypeAliasMaker( psiClass, newPsiClass, features ).generateOrCheck(); + } + finally + { + _reenter.get().remove( qname ); + } + } + +// private TypeAliasMaker(PsiExtensibleClass psiClass, TypeAliasExternalAnnotator.Info issueInfo ) +// { +// this( psiClass, issueInfo, null ); +// } + + private TypeAliasMaker( PsiClass psiClass, PsiClass newPsiClass, LinkedHashSet features ) + { + this( psiClass, newPsiClass, null, features ); + } + + private TypeAliasMaker( PsiClass psiClass, PsiClass newPsiClass, Object issueInfo, LinkedHashSet features ) + { + _psiClass = psiClass; + _newPsiClass = newPsiClass; + _isAbstractClass = psiClass.hasModifierProperty( PsiModifier.ABSTRACT ); + _features = features; + } + + private void generateOrCheck() + { + if( _newPsiClass.getQualifiedName() == null ) + { + return; + } + if( _features != null ) + { + dump2(_features, _newPsiClass ); + } + } + + private void dump2( Set members, PsiClass psiClass ) + { + dump2( members, psiClass.getAllFields() ); + dump2( members, psiClass.getAllMethods() ); + dump2( members, psiClass.getAllInnerClasses() ); + } + + private void dump2( Set members, PsiMember[] psiMembers ) + { + for( PsiMember member : psiMembers ) + { + member = generateMemberFromSource( member ); + if( member != null) + { +// System.out.println(_psiClass + " adding " + member); + members.add(member); + } + } + } + + private PsiMember generateMemberFromSource( PsiMember member ) + { + // If add an abstract method to a non-abstract class, + // it will cause the IDE to prompt `make to abstract`. + if ( !_isAbstractClass && member.hasModifierProperty( PsiModifier.ABSTRACT ) ) + { + return null; + } + + if( !(member instanceof PsiMethod) || !((PsiMethod) member).isConstructor() ) + { + return member; + } + + // Only adding the self-class constructor. + if( member.getContainingClass() != _newPsiClass ) + { + return null; + } + + PsiMethod refMethod = GenerateMembersUtil.substituteGenericMethod( (PsiMethod)member, PsiSubstitutor.EMPTY, _newPsiClass ); + ManPsiElementFactory manPsiElemFactory = ManPsiElementFactory.instance(); + String methodName = _psiClass.getName(); + ManModule manModule = ManProject.getModule( _newPsiClass ); + ManLightMethodBuilder method = manPsiElemFactory.createLightMethod( manModule, _newPsiClass.getManager(), methodName, refMethod.getModifierList() ) + .withNavigationElement( member ) + .withMethodReturnType( refMethod.getReturnType() ) + .withContainingClass( _newPsiClass ) + .withConstructor( true ); + + for( PsiTypeParameter tv : refMethod.getTypeParameters() ) + { + method.withTypeParameterDirect( tv ); + } + + PsiParameter[] parameters = refMethod.getParameterList().getParameters(); + for( PsiParameter psiParameter : parameters ) + { + method.withParameter( psiParameter.getName(), psiParameter.getType() ); + } + + for( PsiClassType psiClassType : refMethod.getThrowsList().getReferencedTypes() ) + { + method.withException( psiClassType ); + } + + return method; + } + + public static PsiAnnotation getAnnotation(PsiClass psiClass ) + { + if( psiClass != null && psiClass.getQualifiedName() != null ) + { + return psiClass.getAnnotation( TypeAlias.class.getTypeName() ); + } + return null; + } + + public static PsiClass getAliasedType(PsiClass psiClass ) + { + PsiAnnotation annotation = getAnnotation( psiClass ); + if( annotation == null ) + { + return null; + } + try + { + for( JvmAnnotationAttribute attribute : annotation.getAttributes() ) + { + String name = attribute.getAttributeName(); + JvmAnnotationAttributeValue value = attribute.getAttributeValue(); + if( !name.equals( "value" ) ) + { + return null; + } + return getAttributeClassValue( value ); + } + return psiClass.getSuperClass(); + } + catch (Exception ex) + { + return null; + } + } + + private static PsiClass getAttributeClassValue( JvmAnnotationAttributeValue value ) + { + if( value instanceof JvmAnnotationArrayValue ) + { + for( JvmAnnotationAttributeValue cls : ((JvmAnnotationArrayValue)value).getValues() ) + { + return getAttributeClassValue( cls ); + } + } + if( value instanceof JvmAnnotationClassValue ) + { + return (PsiClass)((JvmAnnotationClassValue)value).getClazz(); + } + return null; + } +} diff --git a/src/main/java/manifold/ij/psi/ManLightMethodBuilder.java b/src/main/java/manifold/ij/psi/ManLightMethodBuilder.java index 2003663..ec817ba 100644 --- a/src/main/java/manifold/ij/psi/ManLightMethodBuilder.java +++ b/src/main/java/manifold/ij/psi/ManLightMethodBuilder.java @@ -55,6 +55,8 @@ public interface ManLightMethodBuilder extends PsiMethod ManLightMethodBuilder withAdditionalModule( ManModule module ); + ManLightMethodBuilder withConstructor( boolean constructor ); + ManModule getModule(); Set getModules(); } diff --git a/src/main/java/manifold/ij/psi/ManLightMethodBuilderImpl.java b/src/main/java/manifold/ij/psi/ManLightMethodBuilderImpl.java index afd3d3a..b209e1b 100644 --- a/src/main/java/manifold/ij/psi/ManLightMethodBuilderImpl.java +++ b/src/main/java/manifold/ij/psi/ManLightMethodBuilderImpl.java @@ -99,6 +99,13 @@ public ManLightMethodBuilder withModifier( @PsiModifier.ModifierConstant String return this; } + @Override + public ManLightMethodBuilder withConstructor( boolean constructor ) + { + setConstructor( constructor ); + return this; + } + @Override public ManLightMethodBuilder withMethodReturnType( PsiType returnType ) { diff --git a/src/main/java/manifold/ij/util/ManPsiUtil.java b/src/main/java/manifold/ij/util/ManPsiUtil.java index b5dbae8..bc5bc27 100644 --- a/src/main/java/manifold/ij/util/ManPsiUtil.java +++ b/src/main/java/manifold/ij/util/ManPsiUtil.java @@ -22,9 +22,11 @@ import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.PsiClassImplUtil; import manifold.ext.rt.api.Structural; import manifold.ij.core.ManModule; import manifold.ij.core.ManProject; +import org.jetbrains.annotations.NotNull; import java.util.concurrent.Callable; @@ -38,6 +40,10 @@ public static boolean isStructuralInterface( PsiClass psiClass ) return structuralAnno != null; } + public static boolean isClassEquivalentTo(@NotNull PsiClass aClass, PsiElement another) { + return PsiClassImplUtil.isClassEquivalentTo(aClass, another); + } + public static void runInTypeManifoldLoader( PsiElement context, Runnable code ) { ManModule module = ManProject.getModule( context ); diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 6b60e99..7364f29 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -489,6 +489,9 @@ Other minor fixes and improvements. + + @@ -500,17 +503,21 @@ Other minor fixes and improvements. + + + +