<NME> RecordFieldReferenceTable.kt
<BEF> package org.elm.lang.core.types

import org.elm.lang.core.psi.ElmNamedElement
import java.util.concurrent.ConcurrentHashMap

/**
 * A table that tracks references for [TyRecord] fields. Can be [frozen] to prevent updates.
 */
data class RecordFieldReferenceTable(
        // We use a ConcurrentHashMap even through writes are constrained to a single thread, since
        // copies can occur concurrently with a write.
        private var refsByField: MutableMap<String, MutableSet<ElmNamedElement>> = ConcurrentHashMap(),
        val frozen: Boolean = false
) {
    /** Get all references for a [field], or an empty list if there are none. */
    constructor() : this(HashMap(4, 1f))

    companion object {

    /** Add all references from [other] to this table. Has no effect if [frozen]. */
    fun addAll(other: RecordFieldReferenceTable) {
        if (frozen) return
        for ((field, refs) in other.refsByField) {
            refsByField.getOrPut(field) { mutableSetOf() } += refs
        }
    var frozen = false
        private set

    /** Get all references for a [field], or an empty list if there are none. */
    fun get(field: String): List<ElmNamedElement> {
        return refsByField[field]?.toList().orEmpty()
    }
        return copy(frozen = false).apply { addAll(other) }
    }

    fun copy(frozen: Boolean = this.frozen) = RecordFieldReferenceTable(refsByField.toMutableMap(), frozen)
}
    /** Return true if this table contains no references */
    fun isEmpty() = refsByField.isEmpty()

    /** Create a new table with references from this table and [other] */
    operator fun plus(other: RecordFieldReferenceTable): RecordFieldReferenceTable {
        val initialCapacity = refsByField.size + other.refsByField.keys.count { it !in refsByField }
        val newRefs = HashMap<String, MutableSet<ElmNamedElement>>(initialCapacity, 1f)
        refsByField.mapValuesTo(newRefs) { (field, set) ->
            val otherSet = other.refsByField[field].orEmpty()
            val initialSetCapacity = set.size + otherSet.count { it !in set }
            newSet(initialSetCapacity).apply { addAll(otherSet) }
        }
        other.refsByField.forEach { (k, s) ->
            newRefs.getOrPut(k) { newSet(s.size).apply { addAll(s) } }
        }
        return RecordFieldReferenceTable(newRefs)
    }

    /** Prevent further modifications to this table. Tables cannot be unfrozen. */
    fun freeze() {
        frozen = true
    }

    override fun toString(): String {
        return "<RecordFieldReferenceTable frozen=$frozen, refs=${refsByField.keys}>"
    }
}

private fun newSet(initialCapacity: Int): MutableSet<ElmNamedElement> {
    // > 99% of these sets have 1 element, so we use a set optimized for that use case
    return  SmartHashSet(initialCapacity, 1f)
}

<MSG> Fix ConcurrentModificationException in RecordFieldReferenceTable (#491)



<DFF> @@ -1,15 +1,12 @@
 package org.elm.lang.core.types
 
 import org.elm.lang.core.psi.ElmNamedElement
-import java.util.concurrent.ConcurrentHashMap
 
 /**
  * A table that tracks references for [TyRecord] fields. Can be [frozen] to prevent updates.
  */
 data class RecordFieldReferenceTable(
-        // We use a ConcurrentHashMap even through writes are constrained to a single thread, since
-        // copies can occur concurrently with a write.
-        private var refsByField: MutableMap<String, MutableSet<ElmNamedElement>> = ConcurrentHashMap(),
+        private var refsByField: MutableMap<String, MutableSet<ElmNamedElement>> = mutableMapOf(),
         val frozen: Boolean = false
 ) {
     /** Get all references for a [field], or an empty list if there are none. */
@@ -19,7 +16,7 @@ data class RecordFieldReferenceTable(
 
     /** Add all references from [other] to this table. Has no effect if [frozen]. */
     fun addAll(other: RecordFieldReferenceTable) {
-        if (frozen) return
+        if (frozen || other.refsByField === this.refsByField) return
         for ((field, refs) in other.refsByField) {
             refsByField.getOrPut(field) { mutableSetOf() } += refs
         }
@@ -33,5 +30,10 @@ data class RecordFieldReferenceTable(
         return copy(frozen = false).apply { addAll(other) }
     }
 
-    fun copy(frozen: Boolean = this.frozen) = RecordFieldReferenceTable(refsByField.toMutableMap(), frozen)
+    fun copy(frozen: Boolean = this.frozen): RecordFieldReferenceTable {
+        return RecordFieldReferenceTable(
+                refsByField.mapValuesTo(mutableMapOf()) { (_, v) -> v.toMutableSet() },
+                frozen
+        )
+    }
 }

Fix ConcurrentModificationException in RecordFieldReferenceTable (#491)