blob: 0a13de43d5326e1357c87f8859d9d6015e0cad47 [file] [log] [blame]
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
class UnificationContext {
constructor(typeParameters)
{
this._typeParameters = new Set(typeParameters);
this._nextMap = new Map();
this._extraNodes = new Set();
}
union(a, b)
{
a = this.find(a);
b = this.find(b);
if (a == b)
return;
if (!a.isUnifiable) {
[a, b] = [b, a];
if (!a.isUnifiable)
throw new Error("Cannot unify non-unifiable things " + a + " and " + b);
}
// Make sure that type parameters don't end up being roots.
if (a.isUnifiable && b.isUnifiable && this._typeParameters.has(b))
[a, b] = [b, a];
this._nextMap.set(a, b);
}
find(node)
{
let currentNode = node;
let nextNode = this._nextMap.get(currentNode);
if (!nextNode)
return currentNode;
for (;;) {
currentNode = nextNode;
nextNode = this._nextMap.get(currentNode);
if (!nextNode)
break;
}
this._nextMap.set(node, currentNode);
return currentNode;
}
addExtraNode(node)
{
this._extraNodes.add(node);
}
get nodes()
{
let result = new Set();
for (let [key, value] of this._nextMap) {
result.add(key);
result.add(value);
}
for (let node of this._extraNodes)
result.add(node);
return result;
}
typeParameters() { return this._typeParameters; }
*typeArguments()
{
for (let typeArgument of this.nodes) {
if (!typeArgument.isUnifiable)
continue;
if (this._typeParameters.has(typeArgument))
continue;
yield typeArgument;
}
}
verify()
{
// We do a two-phase pre-verification. This gives literals a chance to select a more specific type.
let preparations = [];
for (let node of this.nodes) {
let preparation = node.prepareToVerify(this);
if (preparation)
preparations.push(preparation);
}
for (let preparation of preparations) {
let result = preparation();
if (!result.result)
return result;
}
for (let typeParameter of this._typeParameters) {
let result = typeParameter.verifyAsParameter(this);
if (!result.result)
return result;
}
let numTypeVariableArguments = 0;
let argumentSet = new Set();
for (let typeArgument of this.typeArguments()) {
let result = typeArgument.verifyAsArgument(this);
if (!result.result)
return result;
if (typeArgument.isLiteral)
continue;
argumentSet.add(this.find(typeArgument));
numTypeVariableArguments++;
}
if (argumentSet.size == numTypeVariableArguments)
return {result: true};
return {result: false, reason: "Type variables used as arguments got unified with each other"};
}
get conversionCost()
{
let result = 0;
for (let typeArgument of this.typeArguments())
result += typeArgument.conversionCost(this);
return result;
}
commit()
{
for (let typeArgument of this.typeArguments())
typeArgument.commitUnification(this);
}
}