diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 1e6ccc47341..f299e963daf 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.VarianceConflictDiagnosticData; import org.jetbrains.kotlin.resolve.calls.inference.InferenceErrorData; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; +import org.jetbrains.kotlin.resolve.calls.tower.WrongResolutionToClassifier; import org.jetbrains.kotlin.types.KotlinType; import java.lang.reflect.Field; @@ -531,6 +532,9 @@ public interface Errors { DiagnosticFactory1 REIFIED_TYPE_FORBIDDEN_SUBSTITUTION = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 REIFIED_TYPE_UNSAFE_SUBSTITUTION = DiagnosticFactory1.create(WARNING); + DiagnosticFactory3 RESOLUTION_TO_CLASSIFIER = + DiagnosticFactory3.create(ERROR); + // Type inference DiagnosticFactory0 CANNOT_INFER_PARAMETER_TYPE = DiagnosticFactory0.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index c46c63b193c..3b1f715f035 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -303,6 +303,7 @@ public class DefaultErrorMessages { MAP.put(DELEGATION_IN_INTERFACE, "Interfaces cannot use delegation"); MAP.put(DELEGATION_NOT_TO_INTERFACE, "Only interfaces can be delegated to"); MAP.put(NO_CONSTRUCTOR, "This class does not have a constructor"); + MAP.put(RESOLUTION_TO_CLASSIFIER, "{2}", NAME, TO_STRING, STRING); MAP.put(NOT_A_CLASS, "Not a class"); MAP.put(ILLEGAL_ESCAPE_SEQUENCE, "Illegal escape sequence"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/NewResolutionOldInference.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/NewResolutionOldInference.kt index 32bc0ea5d64..e5caaf522ae 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/NewResolutionOldInference.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/tower/NewResolutionOldInference.kt @@ -17,12 +17,15 @@ package org.jetbrains.kotlin.resolve.calls.tower import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.incremental.components.LookupLocation import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.Call +import org.jetbrains.kotlin.psi.KtReferenceExpression +import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.TemporaryBindingTrace import org.jetbrains.kotlin.resolve.calls.CallTransformer import org.jetbrains.kotlin.resolve.calls.CandidateResolver @@ -157,6 +160,13 @@ class NewResolutionOldInference( } val candidates = towerResolver.runResolve(scopeTower, processor, useOrder = kind != ResolutionKind.CallableReference) + + if (candidates.isEmpty()) { + if (reportAdditionalDiagnosticIfNoCandidates(context, name, kind, scopeTower, detailedReceiver)) { + return OverloadResolutionResultsImpl.nameNotFound() + } + } + return convertToOverloadResults(candidates, tracing, context) } @@ -242,6 +252,32 @@ class NewResolutionOldInference( return resolutionResultsHandler.computeResultAndReportErrors(basicCallContext, tracing, resolvedCalls) } + // true if we found something + private fun reportAdditionalDiagnosticIfNoCandidates( + context: BasicCallResolutionContext, + name: Name, + kind: ResolutionKind<*>, + scopeTower: ImplicitScopeTower, + detailedReceiver: DetailedReceiver? + ): Boolean { + val reference = context.call.calleeExpression as? KtReferenceExpression ?: return false + + val errorCadidates = when (kind) { + ResolutionKind.Function -> collectErrorCandidatesForFunction(scopeTower, name, detailedReceiver) + ResolutionKind.Variable -> collectErrorCandidatesForVariable(scopeTower, name, detailedReceiver) + else -> emptyList() + } + + for (candidate in errorCadidates) { + if (candidate is ErrorCandidate.Classifier) { + context.trace.record(BindingContext.REFERENCE_TARGET, reference, candidate.descriptor) + context.trace.report(Errors.RESOLUTION_TO_CLASSIFIER.on(reference, candidate.descriptor, candidate.kind, candidate.errorMessage)) + return true + } + } + return false + } + private class ImplicitScopeTowerImpl( val resolutionContext: ResolutionContext<*>, override val dynamicScope: MemberScope, diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ErrorCandidateFactory.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ErrorCandidateFactory.kt new file mode 100644 index 00000000000..e137260173d --- /dev/null +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ErrorCandidateFactory.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2010-2016 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.resolve.calls.tower + +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.calls.tower.WrongResolutionToClassifier.* +import org.jetbrains.kotlin.resolve.scopes.receivers.DetailedReceiver +import org.jetbrains.kotlin.resolve.scopes.receivers.QualifierReceiver +import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValueWithSmartCastInfo +import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier +import org.jetbrains.kotlin.utils.SmartList + +enum class WrongResolutionToClassifier(val message: (Name) -> String) { + TYPE_PARAMETER_AS_VALUE({ "Type parameter $it cannot be used as value" }), + TYPE_PARAMETER_AS_FUNCTION({ "Type parameter $it cannot be called as function" }), + INTERFACE_AS_VALUE({ "Interface $it does not have companion object" }), + INTERFACE_AS_FUNCTION({ "Interface $it does not have constructors" }), + CLASS_AS_VALUE({ "Class $it does not have companion object" }), + OBJECT_AS_FUNCTION({ " Function 'invoke()' is not found in object $it " }) +} + +sealed class ErrorCandidate(val descriptor: D) { + class Classifier(classifierDescriptor: ClassifierDescriptor, val kind: WrongResolutionToClassifier) + : ErrorCandidate(classifierDescriptor) { + val errorMessage = kind.message(descriptor.name) + } +} + +fun collectErrorCandidatesForFunction( + scopeTower: ImplicitScopeTower, + name: Name, + explicitReceiver: DetailedReceiver? +): Collection> { + val context = ErrorCandidateContext(scopeTower, name, explicitReceiver) + context.asClassifierCall(asFunction = true) + return context.result +} + +fun collectErrorCandidatesForVariable( + scopeTower: ImplicitScopeTower, + name: Name, + explicitReceiver: DetailedReceiver? +): Collection> { + val context = ErrorCandidateContext(scopeTower, name, explicitReceiver) + context.asClassifierCall(asFunction = false) + return context.result +} + +private class ErrorCandidateContext( + val scopeTower: ImplicitScopeTower, + val name: Name, + val explicitReceiver: DetailedReceiver? +) { + val result = SmartList>() + + fun add(errorCandidate: ErrorCandidate<*>) { result.add(errorCandidate) } +} + +private fun ErrorCandidateContext.asClassifierCall(asFunction: Boolean) { + val classifier = + when (explicitReceiver) { + is ReceiverValueWithSmartCastInfo -> { + explicitReceiver.receiverValue.type.memberScope.getContributedClassifier(name, scopeTower.location) + } + is QualifierReceiver -> { + explicitReceiver.staticScope.getContributedClassifier(name, scopeTower.location) + } + else -> scopeTower.lexicalScope.findClassifier(name, scopeTower.location) + } ?: return + + val kind = + when (classifier) { + is TypeParameterDescriptor -> if (asFunction) TYPE_PARAMETER_AS_FUNCTION else TYPE_PARAMETER_AS_VALUE + is ClassDescriptor -> { + when (classifier.kind) { + ClassKind.INTERFACE -> if (asFunction) INTERFACE_AS_FUNCTION else INTERFACE_AS_VALUE + ClassKind.OBJECT -> if (asFunction) OBJECT_AS_FUNCTION else return + ClassKind.CLASS -> if (asFunction) return else CLASS_AS_VALUE + else -> return + } + } + else -> return + } + + + add(ErrorCandidate.Classifier(classifier, kind)) +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/ResolveToJava.kt b/compiler/testData/diagnostics/tests/ResolveToJava.kt index c746644064e..e1688e0facf 100644 --- a/compiler/testData/diagnostics/tests/ResolveToJava.kt +++ b/compiler/testData/diagnostics/tests/ResolveToJava.kt @@ -27,7 +27,7 @@ fun test(l : java.util checkSubtype?>(Collections.singleton(1)) Collections.singleton(1.0) - List + List val o = "sdf" as Object diff --git a/compiler/testData/diagnostics/tests/inner/nestedClassAccessedViaInstanceReference.kt b/compiler/testData/diagnostics/tests/inner/nestedClassAccessedViaInstanceReference.kt index 97024c2c7b1..25cc3aac86b 100644 --- a/compiler/testData/diagnostics/tests/inner/nestedClassAccessedViaInstanceReference.kt +++ b/compiler/testData/diagnostics/tests/inner/nestedClassAccessedViaInstanceReference.kt @@ -30,7 +30,7 @@ fun test(with: WithClassObject, without: WithoutClassObject, obj: Obj) { with.NestedWithClassObject.foo() with.NestedEnum.A with.NestedObj - with.NestedObj() + with.NestedObj() with.NestedObj.foo() without.Nested() @@ -39,7 +39,7 @@ fun test(with: WithClassObject, without: WithoutClassObject, obj: Obj) { without.NestedWithClassObject.foo() without.NestedEnum.A without.NestedObj - without.NestedObj() + without.NestedObj() without.NestedObj.foo() obj.Nested() @@ -48,6 +48,6 @@ fun test(with: WithClassObject, without: WithoutClassObject, obj: Obj) { obj.NestedWithClassObject.foo() obj.NestedEnum.A obj.NestedObj - obj.NestedObj() + obj.NestedObj() obj.NestedObj.foo() } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/regressions/kt4827.kt b/compiler/testData/diagnostics/tests/regressions/kt4827.kt index 00982055f05..0c0f96a428c 100644 --- a/compiler/testData/diagnostics/tests/regressions/kt4827.kt +++ b/compiler/testData/diagnostics/tests/regressions/kt4827.kt @@ -11,6 +11,6 @@ class C { } fun f() { - TestInterface() + TestInterface() C.I() } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/resolve/noCandidates/kt2787.kt b/compiler/testData/diagnostics/tests/resolve/noCandidates/kt2787.kt new file mode 100644 index 00000000000..3f14c99c456 --- /dev/null +++ b/compiler/testData/diagnostics/tests/resolve/noCandidates/kt2787.kt @@ -0,0 +1,6 @@ +interface MutableMatrix { +} + +fun toMutableMatrix(): MutableMatrix { + return MutableMatrix() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/resolve/noCandidates/kt2787.txt b/compiler/testData/diagnostics/tests/resolve/noCandidates/kt2787.txt new file mode 100644 index 00000000000..e770a4e95ce --- /dev/null +++ b/compiler/testData/diagnostics/tests/resolve/noCandidates/kt2787.txt @@ -0,0 +1,9 @@ +package + +public fun toMutableMatrix(): MutableMatrix + +public interface MutableMatrix { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifier.kt b/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifier.kt new file mode 100644 index 00000000000..ffe03215465 --- /dev/null +++ b/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifier.kt @@ -0,0 +1,19 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE + +interface A + +object B +class C + +fun test() { + val interface_as_fun = A() + val interface_as_val = A + + val object_as_fun = B() + val class_as_val = C +} + +fun bar() { + val typeParameter_as_val = T + val typeParameter_as_fun = T() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifier.txt b/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifier.txt new file mode 100644 index 00000000000..c44cf941da7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifier.txt @@ -0,0 +1,24 @@ +package + +public fun bar(): kotlin.Unit +public fun test(): kotlin.Unit + +public interface A { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public object B { + private constructor B() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class C { + public constructor C() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifierWithReceiver.kt b/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifierWithReceiver.kt new file mode 100644 index 00000000000..26ffabc8697 --- /dev/null +++ b/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifierWithReceiver.kt @@ -0,0 +1,39 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE + +object X { + interface A + + object B + class C +} + +fun testX() { + val interface_as_fun = X.A() + val interface_as_val = X.A + + val object_as_fun = X.B() + val class_as_val = X.C +} + +class Y { + interface A + + object B + class C +} + +fun testY() { + val interface_as_fun = Y.A() + val interface_as_val = Y.A + + val object_as_fun = Y.B() + val class_as_val = Y.C +} + +fun test(x: X) { + val interface_as_fun = x.A() + val interface_as_val = x.A + + val object_as_fun = x.B() + val class_as_val = x.C +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifierWithReceiver.txt b/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifierWithReceiver.txt new file mode 100644 index 00000000000..be3940fad7b --- /dev/null +++ b/compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifierWithReceiver.txt @@ -0,0 +1,59 @@ +package + +public fun test(/*0*/ x: X): kotlin.Unit +public fun testX(): kotlin.Unit +public fun testY(): kotlin.Unit + +public object X { + private constructor X() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public interface A { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public object B { + private constructor B() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class C { + public constructor C() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public final class Y { + public constructor Y() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public interface A { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public object B { + private constructor B() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class C { + public constructor C() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} diff --git a/compiler/testData/diagnostics/tests/typealias/typeAliasConstructor.kt b/compiler/testData/diagnostics/tests/typealias/typeAliasConstructor.kt index 92e3023bdd6..87576906b3b 100644 --- a/compiler/testData/diagnostics/tests/typealias/typeAliasConstructor.kt +++ b/compiler/testData/diagnostics/tests/typealias/typeAliasConstructor.kt @@ -18,7 +18,7 @@ object AnObject typealias TO = AnObject val test6 = TI() -val test6a = Interface() +val test6a = Interface() val test7 = TO() val test7a = AnObject() diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index ef7e8d683a6..c7d2bcb1462 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -15603,6 +15603,33 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { } } + @TestMetadata("compiler/testData/diagnostics/tests/resolve/noCandidates") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class NoCandidates extends AbstractDiagnosticsTest { + public void testAllFilesPresentInNoCandidates() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/resolve/noCandidates"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("kt2787.kt") + public void testKt2787() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/noCandidates/kt2787.kt"); + doTest(fileName); + } + + @TestMetadata("resolvedToClassifier.kt") + public void testResolvedToClassifier() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifier.kt"); + doTest(fileName); + } + + @TestMetadata("resolvedToClassifierWithReceiver.kt") + public void testResolvedToClassifierWithReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/resolve/noCandidates/resolvedToClassifierWithReceiver.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/tests/resolve/overloadConflicts") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/jps-plugin/testData/incremental/classHierarchyAffected/classBecameInterface/build.log b/jps-plugin/testData/incremental/classHierarchyAffected/classBecameInterface/build.log index da9c695e793..2cdbf5e2693 100644 --- a/jps-plugin/testData/incremental/classHierarchyAffected/classBecameInterface/build.log +++ b/jps-plugin/testData/incremental/classHierarchyAffected/classBecameInterface/build.log @@ -52,10 +52,10 @@ Exit code: ABORT ------------------------------------------ COMPILATION FAILED This class does not have a constructor -Unresolved reference: A -Unresolved reference: A -Unresolved reference: A -Unresolved reference: A +Interface A does not have constructors +Interface A does not have constructors +Interface A does not have constructors +Interface A does not have constructors ================ Step #2 ================= diff --git a/jps-plugin/testData/incremental/withJava/javaUsedInKotlin/samConversions/methodAdded/build.log b/jps-plugin/testData/incremental/withJava/javaUsedInKotlin/samConversions/methodAdded/build.log index 33a8bd0d00d..e3554b2ccda 100644 --- a/jps-plugin/testData/incremental/withJava/javaUsedInKotlin/samConversions/methodAdded/build.log +++ b/jps-plugin/testData/incremental/withJava/javaUsedInKotlin/samConversions/methodAdded/build.log @@ -23,5 +23,5 @@ End of files Exit code: ABORT ------------------------------------------ COMPILATION FAILED -Unresolved reference: SamInterface -Unresolved reference: SamInterface +Interface SamInterface does not have constructors +Interface SamInterface does not have constructors \ No newline at end of file