/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.test.util;
import com.google.common.collect.Lists;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.KotlinTypeKt;
import org.junit.Assert;
import java.io.PrintStream;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
public class DescriptorValidator {
public static void validate(@NotNull ValidationVisitor validationStrategy, DeclarationDescriptor descriptor) {
DiagnosticCollectorForTests collector = new DiagnosticCollectorForTests();
validate(validationStrategy, descriptor, collector);
collector.done();
}
public static void validate(
@NotNull ValidationVisitor validator,
@NotNull DeclarationDescriptor descriptor,
@NotNull DiagnosticCollector collector
) {
RecursiveDescriptorProcessor.process(descriptor, collector, validator);
}
private static void report(@NotNull DiagnosticCollector collector, @NotNull DeclarationDescriptor descriptor, @NotNull String message) {
collector.report(new ValidationDiagnostic(descriptor, message));
}
public interface DiagnosticCollector {
void report(@NotNull ValidationDiagnostic diagnostic);
}
public static class ValidationVisitor implements DeclarationDescriptorVisitor<Boolean, DiagnosticCollector> {
public static ValidationVisitor errorTypesForbidden() {
return new ValidationVisitor();
}
public static ValidationVisitor errorTypesAllowed() {
return new ValidationVisitor().allowErrorTypes();
}
private boolean allowErrorTypes = false;
private Predicate<DeclarationDescriptor> recursiveFilter = descriptor -> true;
protected ValidationVisitor() {
}
@NotNull
public ValidationVisitor withStepIntoFilter(@NotNull Predicate<DeclarationDescriptor> filter) {
this.recursiveFilter = filter;
return this;
}
@NotNull
public ValidationVisitor allowErrorTypes() {
this.allowErrorTypes = true;
return this;
}
protected void validateScope(DeclarationDescriptor scopeOwner, @NotNull MemberScope scope, @NotNull DiagnosticCollector collector) {
for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(scope)) {
if (recursiveFilter.test(descriptor)) {
descriptor.accept(new ScopeValidatorVisitor(collector), scope);
}
}
}
private void validateType(
@NotNull DeclarationDescriptor descriptor,
@Nullable KotlinType type,
@NotNull DiagnosticCollector collector
) {
if (type == null) {
report(collector, descriptor, "No type");
return;
}
if (!allowErrorTypes && KotlinTypeKt.isError(type)) {
report(collector, descriptor, "Error type: " + type);
return;
}
validateScope(descriptor, type.getMemberScope(), collector);
}
private void validateReturnType(CallableDescriptor descriptor, DiagnosticCollector collector) {
validateType(descriptor, descriptor.getReturnType(), collector);
}
private static void validateTypeParameters(DiagnosticCollector collector, List<TypeParameterDescriptor> parameters) {
for (int i = 0; i < parameters.size(); i++) {
TypeParameterDescriptor typeParameterDescriptor = parameters.get(i);
if (typeParameterDescriptor.getIndex() != i) {
report(collector, typeParameterDescriptor, "Incorrect index: " + typeParameterDescriptor.getIndex() + " but must be " + i);
}
}
}
private static void validateValueParameters(DiagnosticCollector collector, List<ValueParameterDescriptor> parameters) {
for (int i = 0; i < parameters.size(); i++) {
ValueParameterDescriptor valueParameterDescriptor = parameters.get(i);
if (valueParameterDescriptor.getIndex() != i) {
report(collector, valueParameterDescriptor, "Incorrect index: " + valueParameterDescriptor.getIndex() + " but must be " + i);
}
}
}
private void validateTypes(
DeclarationDescriptor descriptor,
DiagnosticCollector collector,
Collection<KotlinType> types
) {
for (KotlinType type : types) {
validateType(descriptor, type, collector);
}
}
private void validateCallable(CallableDescriptor descriptor, DiagnosticCollector collector) {
validateReturnType(descriptor, collector);
validateTypeParameters(collector, descriptor.getTypeParameters());
validateValueParameters(collector, descriptor.getValueParameters());
}
private static <T> void assertEquals(
DeclarationDescriptor descriptor,
DiagnosticCollector collector,
String name,
T expected,
T actual
) {
if (!expected.equals(actual)) {
report(collector, descriptor, "Wrong " + name + ": " + actual + " must be " + expected);
}
}
private static <T> void assertEqualTypes(
DeclarationDescriptor descriptor,
DiagnosticCollector collector,
String name,
KotlinType expected,
KotlinType actual
) {
if (KotlinTypeKt.isError(expected) && KotlinTypeKt.isError(actual)) {
assertEquals(descriptor, collector, name, expected.toString(), actual.toString());
}
else if (!expected.equals(actual)) {
report(collector, descriptor, "Wrong " + name + ": " + actual + " must be " + expected);
}
}
private static void validateAccessor(
PropertyDescriptor descriptor,
DiagnosticCollector collector,
PropertyAccessorDescriptor accessor,
String name
) {
// TODO: fix the discrepancies in descriptor construction and enable these checks
//assertEquals(accessor, collector, name + " visibility", descriptor.getVisibility(), accessor.getVisibility());
//assertEquals(accessor, collector, name + " modality", descriptor.getModality(), accessor.getModality());
assertEquals(accessor, collector, "corresponding property", descriptor, accessor.getCorrespondingProperty());
}
@Override
public Boolean visitPackageFragmentDescriptor(
PackageFragmentDescriptor descriptor, DiagnosticCollector collector
) {
validateScope(descriptor, descriptor.getMemberScope(), collector);
return true;
}
@Override
public Boolean visitPackageViewDescriptor(PackageViewDescriptor descriptor, DiagnosticCollector collector) {
if (!recursiveFilter.test(descriptor)) return false;
validateScope(descriptor, descriptor.getMemberScope(), collector);
return true;
}
@Override
public Boolean visitVariableDescriptor(
VariableDescriptor descriptor, DiagnosticCollector collector
) {
validateReturnType(descriptor, collector);
return true;
}
@Override
public Boolean visitFunctionDescriptor(
FunctionDescriptor descriptor, DiagnosticCollector collector
) {
validateCallable(descriptor, collector);
return true;
}
@Override
public Boolean visitTypeParameterDescriptor(
TypeParameterDescriptor descriptor, DiagnosticCollector collector
) {
validateTypes(descriptor, collector, descriptor.getUpperBounds());
validateType(descriptor, descriptor.getDefaultType(), collector);
return true;
}
@Override
public Boolean visitClassDescriptor(
ClassDescriptor descriptor, DiagnosticCollector collector
) {
validateTypeParameters(collector, descriptor.getDeclaredTypeParameters());
Collection<KotlinType> supertypes = descriptor.getTypeConstructor().getSupertypes();
if (supertypes.isEmpty() && descriptor.getKind() != ClassKind.INTERFACE
&& !KotlinBuiltIns.isSpecialClassWithNoSupertypes(descriptor)) {
report(collector, descriptor, "No supertypes for non-trait");
}
validateTypes(descriptor, collector, supertypes);
validateType(descriptor, descriptor.getDefaultType(), collector);
validateScope(descriptor, descriptor.getUnsubstitutedInnerClassesScope(), collector);
List<ConstructorDescriptor> primary = Lists.newArrayList();
for (ConstructorDescriptor constructorDescriptor : descriptor.getConstructors()) {
if (constructorDescriptor.isPrimary()) {
primary.add(constructorDescriptor);
}
}
if (primary.size() > 1) {
report(collector, descriptor, "Many primary constructors: " + primary);
}
ConstructorDescriptor primaryConstructor = descriptor.getUnsubstitutedPrimaryConstructor();
if (primaryConstructor != null) {
if (!descriptor.getConstructors().contains(primaryConstructor)) {
report(collector, primaryConstructor,
"Primary constructor not in getConstructors() result: " + descriptor.getConstructors());
}
}
ClassDescriptor companionObjectDescriptor = descriptor.getCompanionObjectDescriptor();
if (companionObjectDescriptor != null && !companionObjectDescriptor.isCompanionObject()) {
report(collector, companionObjectDescriptor, "Companion object should be marked as such");
}
return true;
}
@Override
public Boolean visitTypeAliasDescriptor(
TypeAliasDescriptor descriptor, DiagnosticCollector data
) {
// TODO typealias
return true;
}
@Override
public Boolean visitModuleDeclaration(
ModuleDescriptor descriptor, DiagnosticCollector collector
) {
return true;
}
@Override
public Boolean visitConstructorDescriptor(
ConstructorDescriptor constructorDescriptor, DiagnosticCollector collector
) {
visitFunctionDescriptor(constructorDescriptor, collector);
assertEqualTypes(constructorDescriptor, collector,
"return type",
constructorDescriptor.getContainingDeclaration().getDefaultType(),
constructorDescriptor.getReturnType());
return true;
}
@Override
public Boolean visitScriptDescriptor(
ScriptDescriptor scriptDescriptor, DiagnosticCollector collector
) {
return true;
}
@Override
public Boolean visitPropertyDescriptor(
PropertyDescriptor descriptor, DiagnosticCollector collector
) {
validateCallable(descriptor, collector);
PropertyGetterDescriptor getter = descriptor.getGetter();
if (getter != null) {
assertEqualTypes(getter, collector, "getter return type", descriptor.getType(), getter.getReturnType());
validateAccessor(descriptor, collector, getter, "getter");
}
PropertySetterDescriptor setter = descriptor.getSetter();
if (setter != null) {
assertEquals(setter, collector, "setter parameter count", 1, setter.getValueParameters().size());
assertEqualTypes(setter, collector, "setter parameter type", descriptor.getType(), setter.getValueParameters().get(0).getType());
assertEquals(setter, collector, "corresponding property", descriptor, setter.getCorrespondingProperty());
}
return true;
}
@Override
public Boolean visitValueParameterDescriptor(
ValueParameterDescriptor descriptor, DiagnosticCollector collector
) {
return visitVariableDescriptor(descriptor, collector);
}
@Override
public Boolean visitPropertyGetterDescriptor(
PropertyGetterDescriptor descriptor, DiagnosticCollector collector
) {
return visitFunctionDescriptor(descriptor, collector);
}
@Override
public Boolean visitPropertySetterDescriptor(
PropertySetterDescriptor descriptor, DiagnosticCollector collector
) {
return visitFunctionDescriptor(descriptor, collector);
}
@Override
public Boolean visitReceiverParameterDescriptor(
ReceiverParameterDescriptor descriptor, DiagnosticCollector collector
) {
validateType(descriptor, descriptor.getType(), collector);
return true;
}
}
private static class ScopeValidatorVisitor implements DeclarationDescriptorVisitor<Void, MemberScope> {
private final DiagnosticCollector collector;
public ScopeValidatorVisitor(DiagnosticCollector collector) {
this.collector = collector;
}
private void report(DeclarationDescriptor expected, String message) {
DescriptorValidator.report(collector, expected, message);
}
private void assertFound(
@NotNull MemberScope scope,
@NotNull DeclarationDescriptor expected,
@Nullable DeclarationDescriptor found,
boolean shouldBeSame
) {
if (found == null) {
report(expected, "Not found in " + scope);
}
if (shouldBeSame ? expected != found : !expected.equals(found)) {
report(expected, "Lookup error in " + scope + ": " + found);
}
}
private void assertFound(
@NotNull MemberScope scope,
@NotNull DeclarationDescriptor expected,
@NotNull Collection<? extends DeclarationDescriptor> found
) {
if (!found.contains(expected)) {
report(expected, "Not found in " + scope + ": " + found);
}
}
@Override
public Void visitPackageFragmentDescriptor(
PackageFragmentDescriptor descriptor, MemberScope scope
) {
return null;
}
@Override
public Void visitPackageViewDescriptor(
PackageViewDescriptor descriptor, MemberScope scope
) {
return null;
}
@Override
public Void visitVariableDescriptor(
VariableDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedVariables(descriptor.getName(), NoLookupLocation.FROM_TEST));
return null;
}
@Override
public Void visitFunctionDescriptor(
FunctionDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedFunctions(descriptor.getName(), NoLookupLocation.FROM_TEST));
return null;
}
@Override
public Void visitTypeParameterDescriptor(
TypeParameterDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedClassifier(descriptor.getName(), NoLookupLocation.FROM_TEST), true);
return null;
}
@Override
public Void visitClassDescriptor(
ClassDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedClassifier(descriptor.getName(), NoLookupLocation.FROM_TEST), true);
return null;
}
@Override
public Void visitTypeAliasDescriptor(TypeAliasDescriptor descriptor, MemberScope data) {
// TODO typealias
return null;
}
@Override
public Void visitModuleDeclaration(
ModuleDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Module found in scope: " + scope);
return null;
}
@Override
public Void visitConstructorDescriptor(
ConstructorDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Constructor found in scope: " + scope);
return null;
}
@Override
public Void visitScriptDescriptor(
ScriptDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Script found in scope: " + scope);
return null;
}
@Override
public Void visitPropertyDescriptor(
PropertyDescriptor descriptor, MemberScope scope
) {
return visitVariableDescriptor(descriptor, scope);
}
@Override
public Void visitValueParameterDescriptor(
ValueParameterDescriptor descriptor, MemberScope scope
) {
return visitVariableDescriptor(descriptor, scope);
}
@Override
public Void visitPropertyGetterDescriptor(
PropertyGetterDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Getter found in scope: " + scope);
return null;
}
@Override
public Void visitPropertySetterDescriptor(
PropertySetterDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Setter found in scope: " + scope);
return null;
}
@Override
public Void visitReceiverParameterDescriptor(
ReceiverParameterDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Receiver parameter found in scope: " + scope);
return null;
}
}
public static class ValidationDiagnostic {
private final DeclarationDescriptor descriptor;
private final String message;
private final Throwable stackTrace;
private ValidationDiagnostic(@NotNull DeclarationDescriptor descriptor, @NotNull String message) {
this.descriptor = descriptor;
this.message = message;
this.stackTrace = new Throwable();
}
@NotNull
public DeclarationDescriptor getDescriptor() {
return descriptor;
}
@NotNull
public String getMessage() {
return message;
}
@NotNull
public Throwable getStackTrace() {
return stackTrace;
}
public void printStackTrace(@NotNull PrintStream out) {
out.println(descriptor);
out.println(message);
stackTrace.printStackTrace(out);
}
@Override
public String toString() {
return descriptor + " > " + message;
}
}
private static class DiagnosticCollectorForTests implements DiagnosticCollector {
private boolean errorsFound = false;
@Override
public void report(@NotNull ValidationDiagnostic diagnostic) {
diagnostic.printStackTrace(System.err);
errorsFound = true;
}
public void done() {
if (errorsFound) {
Assert.fail("Descriptor validation failed (see messages above)");
}
}
}
private DescriptorValidator() {}
}