diff --git a/.idea/dictionaries/Nikolay_Krasko.xml b/.idea/dictionaries/Nikolay_Krasko.xml index ad612d5d66e..ee5f1157ac9 100644 --- a/.idea/dictionaries/Nikolay_Krasko.xml +++ b/.idea/dictionaries/Nikolay_Krasko.xml @@ -4,6 +4,7 @@ accessors goto gradle + intrinsics kdoc kompiler memoize diff --git a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/builtins/DefaultObjectMapping.kt b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/builtins/DefaultObjectMapping.kt new file mode 100644 index 00000000000..18830f60367 --- /dev/null +++ b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/builtins/DefaultObjectMapping.kt @@ -0,0 +1,34 @@ +/* + * 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.backend.common.builtins + +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.resolve.DescriptorUtils + +public abstract class DefaultObjectMapping { + public fun hasMappingToObject(classDescriptor: ClassDescriptor): Boolean { + if (!DescriptorUtils.isDefaultObject(classDescriptor)) return false + + val containingDeclaration = classDescriptor.getContainingDeclaration() + if (containingDeclaration !is ClassDescriptor) return false + + return KotlinBuiltIns.isPrimitiveType(containingDeclaration.getDefaultType()) || + KotlinBuiltIns.getInstance().getString() == containingDeclaration || + KotlinBuiltIns.getInstance().getEnum() == containingDeclaration + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FieldInfo.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/FieldInfo.java index 13b27e094ad..096cc68ac60 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FieldInfo.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FieldInfo.java @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.codegen; import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicObjects; import org.jetbrains.kotlin.codegen.state.JetTypeMapper; import org.jetbrains.kotlin.descriptors.ClassDescriptor; import org.jetbrains.kotlin.descriptors.ClassKind; @@ -33,7 +34,7 @@ public class FieldInfo { throw new UnsupportedOperationException("Can't create singleton field for class: " + classDescriptor); } - if (kind == ClassKind.OBJECT) { + if (kind == ClassKind.OBJECT || IntrinsicObjects.INSTANCE$.hasMappingToObject(classDescriptor)) { Type type = typeMapper.mapType(classDescriptor); return new FieldInfo(type, type, JvmAbi.INSTANCE_FIELD, true); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicObjects.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicObjects.kt new file mode 100644 index 00000000000..24d7338f5be --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicObjects.kt @@ -0,0 +1,32 @@ +/* + * 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.codegen.intrinsics + +import org.jetbrains.kotlin.backend.common.builtins.DefaultObjectMapping +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +public object IntrinsicObjects : DefaultObjectMapping() { + public fun mapType(classDescriptor: ClassDescriptor): FqName? { + if (!hasMappingToObject(classDescriptor)) return null + + val containingDeclaration = classDescriptor.getContainingDeclaration() + val name = Name.identifier(containingDeclaration.getName().asString() + "DefaultObject") + return FqName("kotlin.jvm.internal").child(name) + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java index 6c0925ea2a2..ecd9ad4aeab 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java @@ -20,6 +20,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicObjects; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.codegen.*; import org.jetbrains.kotlin.codegen.binding.CalculatedClosure; @@ -293,6 +294,18 @@ public class JetTypeMapper { return Type.getType("[" + boxType(mapType(memberType, kind)).getDescriptor()); } + if (descriptor instanceof ClassDescriptor) { + FqName defaultObjectMappedFqName = IntrinsicObjects.INSTANCE$.mapType((ClassDescriptor) descriptor); + if (defaultObjectMappedFqName != null) { + Type asmType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(defaultObjectMappedFqName); + if (signatureVisitor != null) { + signatureVisitor.writeAsmType(asmType); + } + + return asmType; + } + } + if (descriptor instanceof ClassDescriptor) { Type asmType = computeAsmType((ClassDescriptor) descriptor.getOriginal()); writeGenericType(signatureVisitor, asmType, jetType, howThisTypeIsUsed, projectionsAllowed); @@ -399,10 +412,10 @@ public class JetTypeMapper { else { Variance projectionKind = projectionsAllowed ? getEffectiveVariance( - parameter.getVariance(), - argument.getProjectionKind(), - howThisTypeIsUsed - ) + parameter.getVariance(), + argument.getProjectionKind(), + howThisTypeIsUsed + ) : Variance.INVARIANT; signatureVisitor.writeTypeArgument(projectionKind); diff --git a/core/builtins/native/kotlin/Enum.kt b/core/builtins/native/kotlin/Enum.kt index e6d491285ea..7fa7cdd9e5a 100644 --- a/core/builtins/native/kotlin/Enum.kt +++ b/core/builtins/native/kotlin/Enum.kt @@ -22,6 +22,8 @@ package kotlin * information on enum classes. */ public abstract class Enum>(name: String, ordinal: Int): Comparable { + class object {} + /** * Returns the name of this enum constant, exactly as declared in its enum declaration. */ diff --git a/core/builtins/native/kotlin/Numbers.kt b/core/builtins/native/kotlin/Numbers.kt index cb8c6196d8e..f0aba71e7ee 100644 --- a/core/builtins/native/kotlin/Numbers.kt +++ b/core/builtins/native/kotlin/Numbers.kt @@ -61,6 +61,8 @@ public abstract class Number { * values of this type are represented as values of the primitive type `double`. */ public class Double private () : Number, Comparable { + class object : FloatingPointConstants {} + public override fun compareTo(other: Double): Int public fun compareTo(other: Float): Int public fun compareTo(other: Long) : Int @@ -135,6 +137,8 @@ public class Double private () : Number, Comparable { * values of this type are represented as values of the primitive type `float`. */ public class Float private () : Number, Comparable { + class object : FloatingPointConstants {} + public fun compareTo(other: Double): Int public override fun compareTo(other: Float): Int public fun compareTo(other: Long) : Int @@ -210,6 +214,8 @@ public class Float private () : Number, Comparable { * as values of the primitive type `long`. */ public class Long private () : Number, Comparable { + class object {} + public fun compareTo(other: Double): Int public fun compareTo(other: Float) : Int public override fun compareTo(other: Long): Int @@ -293,6 +299,8 @@ public class Long private () : Number, Comparable { * as values of the primitive type `int`. */ public class Int private () : Number, Comparable { + class object {} + public fun compareTo(other: Double): Int public fun compareTo(other: Float) : Int public fun compareTo(other: Long) : Int @@ -376,6 +384,8 @@ public class Int private () : Number, Comparable { * as values of the primitive type `short`. */ public class Short private () : Number, Comparable { + class object {} + public fun compareTo(other: Double): Int public fun compareTo(other: Float) : Int public fun compareTo(other: Long) : Int @@ -451,6 +461,8 @@ public class Short private () : Number, Comparable { * as values of the primitive type `byte`. */ public class Byte private () : Number, Comparable { + class object {} + public fun compareTo(other: Double): Int public fun compareTo(other: Float) : Int public fun compareTo(other: Long) : Int diff --git a/core/builtins/native/kotlin/String.kt b/core/builtins/native/kotlin/String.kt index 7c692eefd1c..08fe3c2c6db 100644 --- a/core/builtins/native/kotlin/String.kt +++ b/core/builtins/native/kotlin/String.kt @@ -21,6 +21,8 @@ package kotlin * implemented as instances of this class. */ public class String : Comparable, CharSequence { + class object {} + /** * Returns a string obtained by concatenating this string with the string representation of the given [other] object. */ diff --git a/core/builtins/src/kotlin/FloatingPointConstants.kt b/core/builtins/src/kotlin/FloatingPointConstants.kt new file mode 100644 index 00000000000..42963f0a022 --- /dev/null +++ b/core/builtins/src/kotlin/FloatingPointConstants.kt @@ -0,0 +1,23 @@ +/* + * 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 kotlin + +public trait FloatingPointConstants { + public val POSITIVE_INFINITY: T + public val NEGATIVE_INFINITY: T + public val NaN: T +} \ No newline at end of file diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/PrimitiveDefaultObjects.kt b/core/runtime.jvm/src/kotlin/jvm/internal/PrimitiveDefaultObjects.kt new file mode 100644 index 00000000000..8eeeecd29a0 --- /dev/null +++ b/core/runtime.jvm/src/kotlin/jvm/internal/PrimitiveDefaultObjects.kt @@ -0,0 +1,37 @@ +/* + * 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 kotlin.jvm.internal + +private object DoubleDefaultObject : FloatingPointConstants { + override val POSITIVE_INFINITY : Double = java.lang.Double.POSITIVE_INFINITY + override val NEGATIVE_INFINITY : Double = java.lang.Double.NEGATIVE_INFINITY + override val NaN : Double = java.lang.Double.NaN +} + +private object FloatDefaultObject : FloatingPointConstants { + override val POSITIVE_INFINITY : Float = java.lang.Float.POSITIVE_INFINITY + override val NEGATIVE_INFINITY : Float = java.lang.Float.NEGATIVE_INFINITY + override val NaN : Float = java.lang.Float.NaN +} + +private object IntDefaultObject {} +private object LongDefaultObject {} +private object ShortDefaultObject {} +private object ByteDefaultObject {} + +private object StringDefaultObject {} +private object EnumDefaultObject {} \ No newline at end of file diff --git a/js/js.libraries/src/internal/primitiveDefaultObjects.kt b/js/js.libraries/src/internal/primitiveDefaultObjects.kt new file mode 100644 index 00000000000..51892373fbe --- /dev/null +++ b/js/js.libraries/src/internal/primitiveDefaultObjects.kt @@ -0,0 +1,37 @@ +/* + * 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 kotlin.js.internal + +private object DoubleDefaultObject : FloatingPointConstants { + override val POSITIVE_INFINITY: Double = js("Number.POSITIVE_INFINITY") + override val NEGATIVE_INFINITY: Double = js("Number.NEGATIVE_INFINITY") + override val NaN: Double = js("Number.NaN") +} + +private object FloatDefaultObject : FloatingPointConstants { + override val POSITIVE_INFINITY : Float = js("Number.POSITIVE_INFINITY") + override val NEGATIVE_INFINITY : Float = js("Number.NEGATIVE_INFINITY") + override val NaN : Float = js("Number.NaN") +} + +private object IntDefaultObject {} +private object LongDefaultObject {} +private object ShortDefaultObject {} +private object ByteDefaultObject {} + +private object StringDefaultObject {} +private object EnumDefaultObject {} diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/Intrinsics.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/Intrinsics.java index ac77d9591c8..515fbdfadc6 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/Intrinsics.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/Intrinsics.java @@ -18,26 +18,29 @@ package org.jetbrains.kotlin.js.translate.intrinsic; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.js.translate.intrinsic.functions.FunctionIntrinsics; +import org.jetbrains.kotlin.js.translate.intrinsic.objects.ObjectIntrinsics; import org.jetbrains.kotlin.js.translate.intrinsic.operation.BinaryOperationIntrinsics; /** * Provides mechanism to substitute method calls /w native constructs directly. */ public final class Intrinsics { - - @NotNull private final FunctionIntrinsics functionIntrinsics = new FunctionIntrinsics(); + private final BinaryOperationIntrinsics binaryOperationIntrinsics = new BinaryOperationIntrinsics(); + private final ObjectIntrinsics objectIntrinsics = new ObjectIntrinsics(); @NotNull public BinaryOperationIntrinsics getBinaryOperationIntrinsics() { return binaryOperationIntrinsics; } - @NotNull - private final BinaryOperationIntrinsics binaryOperationIntrinsics = new BinaryOperationIntrinsics(); - @NotNull public FunctionIntrinsics getFunctionIntrinsics() { return functionIntrinsics; } -} + + @NotNull + public ObjectIntrinsics getObjectIntrinsics() { + return objectIntrinsics; + } +} \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/objects/objectsIntrinsics.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/objects/objectsIntrinsics.kt new file mode 100644 index 00000000000..025fa36d7dd --- /dev/null +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/objects/objectsIntrinsics.kt @@ -0,0 +1,59 @@ +/* + * 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.js.translate.intrinsic.objects + +import com.google.dart.compiler.backend.js.ast.JsArrayAccess +import com.google.dart.compiler.backend.js.ast.JsExpression +import org.jetbrains.kotlin.backend.common.builtins.DefaultObjectMapping +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.js.config.LibrarySourcesConfig +import org.jetbrains.kotlin.js.translate.context.TranslationContext +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +class DefaultClassObjectIntrinsic(val fqName: FqName, val moduleName: String): ObjectIntrinsic { + override fun apply(context: TranslationContext): JsExpression { + val nameRef = context.getQualifiedReference(fqName) + return JsAstUtils.replaceRootReference( + nameRef, + JsArrayAccess(context.namer().kotlin("modules"), context.program().getStringLiteral(moduleName))) + } +} + +public class ObjectIntrinsics : DefaultObjectMapping() { + public fun getIntrinsic(classDescriptor: ClassDescriptor): ObjectIntrinsic { + if (!hasMappingToObject(classDescriptor)) return NO_OBJECT_INTRINSIC + + val containingDeclaration = classDescriptor.getContainingDeclaration() + val name = Name.identifier(containingDeclaration.getName().asString() + "DefaultObject") + + return DefaultClassObjectIntrinsic(FqName("kotlin.js.internal").child(name), LibrarySourcesConfig.STDLIB_JS_MODULE_NAME) + } +} + +public trait ObjectIntrinsic { + fun apply(context: TranslationContext): JsExpression + fun exists(): Boolean = true +} + +object NO_OBJECT_INTRINSIC : ObjectIntrinsic { + override fun apply(context: TranslationContext): JsExpression = + throw UnsupportedOperationException("ObjectIntrinsic#NO_OBJECT_INTRINSIC_#apply") + + override fun exists(): Boolean = false +} \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/DefaultObjectAccessTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/DefaultObjectAccessTranslator.java index 3399c8fcba8..8145c2f7ba5 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/DefaultObjectAccessTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/DefaultObjectAccessTranslator.java @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.js.translate.context.Namer; import org.jetbrains.kotlin.js.translate.context.TemporaryVariable; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.general.AbstractTranslator; +import org.jetbrains.kotlin.js.translate.intrinsic.objects.ObjectIntrinsic; import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils; import org.jetbrains.kotlin.psi.JetReferenceExpression; import org.jetbrains.kotlin.psi.JetSimpleNameExpression; @@ -60,13 +61,24 @@ public class DefaultObjectAccessTranslator extends AbstractTranslator implements private DefaultObjectAccessTranslator(@NotNull DeclarationDescriptor descriptor, @NotNull TranslationContext context) { super(context); - JsExpression fqReference = translateAsFQReference(descriptor, context()); + this.referenceToDefaultObject = generateReferenceToDefaultObject(descriptor, context); + } + + @NotNull + private static JsExpression generateReferenceToDefaultObject(@NotNull DeclarationDescriptor descriptor, @NotNull TranslationContext context) { + if (descriptor instanceof ClassDescriptor) { + ObjectIntrinsic objectIntrinsic = context.intrinsics().getObjectIntrinsics().getIntrinsic((ClassDescriptor) descriptor); + if (objectIntrinsic.exists()) { + return objectIntrinsic.apply(context); + } + } + + JsExpression fqReference = translateAsFQReference(descriptor, context); if (isObject(descriptor) || isEnumEntry(descriptor)) { - this.referenceToDefaultObject = fqReference; - } - else { - this.referenceToDefaultObject = Namer.getDefaultObjectAccessor(fqReference); + return fqReference; } + + return Namer.getDefaultObjectAccessor(fqReference); } @Override diff --git a/jslib_files.xml b/jslib_files.xml index 029bdef4875..2757c8e4e18 100644 --- a/jslib_files.xml +++ b/jslib_files.xml @@ -6,6 +6,7 @@ + diff --git a/libraries/stdlib/src/kotlin/jvm/BuiltinExtensions.kt b/libraries/stdlib/src/kotlin/jvm/BuiltinExtensions.kt new file mode 100644 index 00000000000..b1ea87b283c --- /dev/null +++ b/libraries/stdlib/src/kotlin/jvm/BuiltinExtensions.kt @@ -0,0 +1,34 @@ +/* + * 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 kotlin.jvm + +public val Int.Default.MIN_VALUE: Int get() = java.lang.Integer.MIN_VALUE +public val Int.Default.MAX_VALUE: Int get() = java.lang.Integer.MAX_VALUE + +public val Double.Default.MIN_VALUE: Double get() = java.lang.Double.MIN_VALUE +public val Double.Default.MAX_VALUE: Double get() = java.lang.Double.MAX_VALUE + +public val Float.Default.MIN_VALUE: Float get() = java.lang.Float.MIN_VALUE +public val Float.Default.MAX_VALUE: Float get() = java.lang.Float.MAX_VALUE + +public val Long.Default.MIN_VALUE: Long get() = java.lang.Long.MIN_VALUE +public val Long.Default.MAX_VALUE: Long get() = java.lang.Long.MAX_VALUE + +public val Short.Default.MIN_VALUE: Short get() = java.lang.Short.MIN_VALUE +public val Short.Default.MAX_VALUE: Short get() = java.lang.Short.MAX_VALUE + +public val Byte.Default.MIN_VALUE: Byte get() = java.lang.Byte.MIN_VALUE +public val Byte.Default.MAX_VALUE: Byte get() = java.lang.Byte.MAX_VALUE \ No newline at end of file