Skip to content

Commit

Permalink
Adds custom type guard
Browse files Browse the repository at this point in the history
  • Loading branch information
tinganho committed Jun 2, 2015
1 parent 2cb0dfd commit 373a776
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 17 deletions.
160 changes: 146 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ module ts {
let anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
let noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);

let anySignature = createSignature(undefined, undefined, emptyArray, anyType, 0, false, false);
let unknownSignature = createSignature(undefined, undefined, emptyArray, unknownType, 0, false, false);
let anySignature = createSignature(undefined, undefined, emptyArray, anyType, undefined, 0, false, false);
let unknownSignature = createSignature(undefined, undefined, emptyArray, unknownType, undefined, 0, false, false);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

Oh gosh, these signatures are too long :(


let globals: SymbolTable = {};

Expand Down Expand Up @@ -2766,7 +2766,7 @@ module ts {

function resolveDeclaredMembers(type: InterfaceType): InterfaceTypeWithDeclaredMembers {
if (!(<InterfaceTypeWithDeclaredMembers>type).declaredProperties) {
var symbol = type.symbol;
let symbol = type.symbol;
(<InterfaceTypeWithDeclaredMembers>type).declaredProperties = getNamedMembers(symbol.members);
(<InterfaceTypeWithDeclaredMembers>type).declaredCallSignatures = getSignaturesOfSymbol(symbol.members["__call"]);
(<InterfaceTypeWithDeclaredMembers>type).declaredConstructSignatures = getSignaturesOfSymbol(symbol.members["__new"]);
Expand All @@ -2776,7 +2776,7 @@ module ts {
return <InterfaceTypeWithDeclaredMembers>type;
}

function resolveClassOrInterfaceMembers(type: InterfaceType): void {
function resolveClassOrInterfaceMembers(type: InterfaceType) {

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

Why remove the type annotation?

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

It was just to keep inline with the other void functions.

let target = resolveDeclaredMembers(type);
let members = target.symbol.members;
let callSignatures = target.declaredCallSignatures;
Expand Down Expand Up @@ -2817,20 +2817,21 @@ module ts {
}

function createSignature(declaration: SignatureDeclaration, typeParameters: TypeParameter[], parameters: Symbol[],
resolvedReturnType: Type, minArgumentCount: number, hasRestParameter: boolean, hasStringLiterals: boolean): Signature {
resolvedReturnType: Type, typePredicate: TypePredicate, minArgumentCount: number, hasRestParameter: boolean, hasStringLiterals: boolean): Signature {
let sig = new Signature(checker);
sig.declaration = declaration;
sig.typeParameters = typeParameters;
sig.parameters = parameters;
sig.resolvedReturnType = resolvedReturnType;
sig.typePredicate = typePredicate;
sig.minArgumentCount = minArgumentCount;
sig.hasRestParameter = hasRestParameter;
sig.hasStringLiterals = hasStringLiterals;
return sig;
}

function cloneSignature(sig: Signature): Signature {
return createSignature(sig.declaration, sig.typeParameters, sig.parameters, sig.resolvedReturnType,
return createSignature(sig.declaration, sig.typeParameters, sig.parameters, sig.resolvedReturnType, sig.typePredicate,
sig.minArgumentCount, sig.hasRestParameter, sig.hasStringLiterals);
}

Expand All @@ -2847,7 +2848,7 @@ module ts {
return signature;
});
}
return [createSignature(undefined, classType.localTypeParameters, emptyArray, classType, 0, false, false)];
return [createSignature(undefined, classType.localTypeParameters, emptyArray, classType, undefined, 0, false, false)];
}

function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable {
Expand Down Expand Up @@ -3219,7 +3220,24 @@ module ts {
}

let returnType: Type;
if (classType) {
let typePredicate: TypePredicate;
if (declaration.typePredicate) {
returnType = booleanType;
let typePredicateNode = declaration.typePredicate;
let links = getNodeLinks(typePredicateNode);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

I would make one function that populates the node links for type predicates, and call it from both places that need these properties.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

I also feel like it's redundant to store these pieces of data on both the links and the type predicate object on the signature.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

We need to at least store it on the links. Because parameter index and the type needs to be calculated on two places independently. I store the type predicate on the signature so I can run checks on the signature directly without getting the node link first. I also think type predicate should be stored in the signature.

if (links.typePredicateParameterIndex === undefined) {
links.typePredicateParameterIndex = getTypePredicateParameterIndex(declaration.parameters, typePredicateNode.parameterName);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

What if this is -1? Probably need to do some check to make sure it matched something, and also, give a different error if it matched a binding element.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

Nvm, I see the check you have later on.

}
if (!links.typeFromTypePredicate) {
links.typeFromTypePredicate = getTypeFromTypeNode(declaration.typePredicate.type);
}
typePredicate = {
parameterName: typePredicateNode.parameterName ? typePredicateNode.parameterName.text : undefined,
parameterIndex: typePredicateNode.parameterName ? links.typePredicateParameterIndex : undefined,
type: links.typeFromTypePredicate

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

These lines should be indented 4 spaces from the outer block.

};
}
else if (classType) {
returnType = classType;

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

I would put this branch before the type predicate branch. Not a big deal, but then a constructor always returns its class, even if it mistakenly has a type predicate.

}
else if (declaration.type) {
Expand All @@ -3238,7 +3256,7 @@ module ts {
}
}

links.resolvedSignature = createSignature(declaration, typeParameters, parameters, returnType,
links.resolvedSignature = createSignature(declaration, typeParameters, parameters, returnType, typePredicate,
minArgumentCount, hasRestParameters(declaration), hasStringLiterals);
}
return links.resolvedSignature;
Expand Down Expand Up @@ -3944,9 +3962,13 @@ module ts {
freshTypeParameters = instantiateList(signature.typeParameters, mapper, instantiateTypeParameter);
mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper);
}
if (signature.typePredicate) {
signature.typePredicate.type = instantiateType(signature.typePredicate.type, mapper);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

This should not be assigned to signature.typePredicate.type. That will overwrite the type predicate of the original signature. It should be assigned to a local, and passed to the new signature.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

fixed.

}
let result = createSignature(signature.declaration, freshTypeParameters,
instantiateList(signature.parameters, mapper, instantiateSymbol),
signature.resolvedReturnType ? instantiateType(signature.resolvedReturnType, mapper) : undefined,
signature.typePredicate,
signature.minArgumentCount, signature.hasRestParameter, signature.hasStringLiterals);
result.target = signature;
result.mapper = mapper;
Expand Down Expand Up @@ -4614,6 +4636,43 @@ module ts {
}
result &= related;
}

if (source.typePredicate && target.typePredicate) {
if (source.typePredicate.parameterIndex !== target.typePredicate.parameterIndex ||

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

What if parameterIndex is undefined? What should happen?

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

If both are undefined, then both are legit if one of them is undefined the assignment is not legit. I also added a return statement below for Ternary.True.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 3, 2015

Where did you add Ternary.True? I do not see it. That is probably not the correct thing, you want to return result in case it is Maybe

source.typePredicate.type.symbol !== target.typePredicate.type.symbol) {

if (reportErrors) {
let sourceParamText = source.typePredicate.parameterName;
let targetParamText = target.typePredicate.parameterName;
let sourceTypeText = typeToString(source.typePredicate.type);
let targetTypeText = typeToString(target.typePredicate.type);

if (source.typePredicate.parameterIndex !== target.typePredicate.parameterIndex) {
reportError(Diagnostics.Parameter_index_from_0_does_not_match_the_parameter_index_from_1,
sourceParamText,
targetParamText);
}
if (source.typePredicate.type.symbol !== target.typePredicate.type.symbol) {

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

I would make this else if

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

Also, I think rather than checking if the symbols are the same, better to call isTypeIdenticalTo on the types.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

I guess it is better to compute it as soon as you figure out that the parameterIndex is not the same.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

Also, because you will be checking identity, there will be no intermediate result that needs to be &ed with result

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Why else if? Then I wouldn't get the error detail if both the parameter index and the type is wrong? I would only get the error details of one of them.

I changed the type detection to use isTypeIdenticalTo.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Also, because you will be checking identity, there will be no intermediate result that needs to be &ed with result

I don't understand this?

Maybe related, I only do a return directly instead of & the result — is it correct?

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 3, 2015

The issue with the errors is that a call to reportError will nest the error inside the subsequent call. Your intent is to report these errors as peers. But that is not supported in reportError because it reports in a linked list fashion, not a tree. We do not report error siblings in relation checking.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 3, 2015

If you want to return false, you return Ternary.False. If you want to return true, there are two kinds of true: True and Maybe. The running value is stored in the result variable in this function. It will already have a value from checking the parameters above. The running value needs to be &ed with whatever you compute. Note that below it's &ed with the value from the return type. So if your check here succeeds, you cannot return, because the return type still needs to be checked.

Since you are not doing any nested type comparisons (except for identity), you don't have to do anything with result. The correct thing is to return Ternary.False if it fails, and just keep going if it succeeds.

reportError(Diagnostics.Type_0_is_not_assignable_to_type_1,
sourceTypeText,
targetTypeText);
}

reportError(Diagnostics.Type_guard_annotation_0_is_not_assignable_to_1,
`${sourceParamText} is ${sourceTypeText}`,
`${targetParamText} is ${targetTypeText}`);
}

return Ternary.False;
}
}
else if (!source.typePredicate && target.typePredicate) {
if (reportErrors) {
reportError(Diagnostics.A_non_type_guard_function_is_not_assignable_to_a_type_guard_function);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

Hmm. This check is going to error on overloads, preventing a particularly useful pattern:

function is(x: any, kindString: "A"): x is A;
function is(x: any, kindString: "B"): x is B;
function is(x: any, kindString: string): boolean {
    return x.kind === kindString;
}

Maybe we should remove this requirement.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

If we remove this check — we wouldn't have an error check for:

declare function test(x: (item) => item is A);
declare function retBool(item): boolean;
test(retBool); // Error

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Is there a way of special case it for overloads?

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 3, 2015

Yeah you are right. Really what I am talking about is just an instance of the problem described in microsoft#943

}
return Ternary.False;
}

let t = getReturnTypeOfSignature(target);
if (t === voidType) return result;
let s = getReturnTypeOfSignature(source);
Expand Down Expand Up @@ -5146,6 +5205,13 @@ module ts {

function inferFromSignature(source: Signature, target: Signature) {
forEachMatchingParameterType(source, target, inferFromTypes);
if (source.typePredicate &&
target.typePredicate &&
target.typePredicate.parameterIndex === source.typePredicate.parameterIndex) {

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

I think the parameter index comparison should be in a nested if check, so that you can still return if both sides have type predicates.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Fixed.


inferFromTypes(source.typePredicate.type, target.typePredicate.type);
return;

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

Add a quick comment explaining why it's okay to ignore the return types if there are type predicates on both sides.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Fixed.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 3, 2015

If we do @CyrusNajmabadi's new proposal, I think you'd have to remove this return statement

}
inferFromTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target));
}

Expand Down Expand Up @@ -5527,7 +5593,7 @@ module ts {
let targetType: Type;
let prototypeProperty = getPropertyOfType(rightType, "prototype");
if (prototypeProperty) {
// Target type is type of the protoype property
// Target type is type of the prototype property
let prototypePropertyType = getTypeOfSymbol(prototypeProperty);
if (prototypePropertyType !== anyType) {
targetType = prototypePropertyType;
Expand All @@ -5543,7 +5609,6 @@ module ts {
else if (rightType.flags & TypeFlags.Anonymous) {
constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
}

if (constructSignatures && constructSignatures.length) {
targetType = getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature))));
}
Expand All @@ -5563,10 +5628,38 @@ module ts {
return type;
}

function narrowTypeByTypePredicate(type: Type, expr: CallExpression, assumeTrue: boolean): Type {
if (type.flags & TypeFlags.Any) {
return type;
}
let signature = getResolvedSignature(expr);
if (!assumeTrue) {
if (type.flags & TypeFlags.Union && signature.typePredicate) {
return getUnionType(filter((<UnionType>type).types, t => !isTypeSubtypeOf(t, signature.typePredicate.type)));

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

This is missing the check for the parameter index.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Fixed.

}
return type;
}
if (signature.typePredicate) {
if (expr.arguments && expr.arguments[signature.typePredicate.parameterIndex]) {

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

What if parameterIndex is undefined?

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Th expression expr.arguments[signature.typePredicate.parameterIndex] would return undefined?

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 3, 2015

I guess I am really not used to implicitly indexing with undefined. I understand the appeal because it is concise, but I would prefer to explicitly check all the cases where we would index with undefined.

if (getSymbolAtLocation(expr.arguments[signature.typePredicate.parameterIndex]) === symbol) {
if (isTypeSubtypeOf(signature.typePredicate.type, type)) {
return signature.typePredicate.type;
}
if (type.flags & TypeFlags.Union) {
return getUnionType(filter((<UnionType>type).types, t => isTypeSubtypeOf(t, signature.typePredicate.type)));
}

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

Can any of this logic be shared with narrowTypeByInstanceof?

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Let me check if I can re-factor it into a function and let them both calling it.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Fixed.

}
}
}
return type;
}

// Narrow the given type based on the given expression having the assumed boolean value. The returned type
// will be a subtype or the same type as the argument.
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
switch (expr.kind) {
case SyntaxKind.CallExpression:
return narrowTypeByTypePredicate(type, <CallExpression>expr, assumeTrue);
case SyntaxKind.ParenthesizedExpression:
return narrowType(type, (<ParenthesizedExpression>expr).expression, assumeTrue);
case SyntaxKind.BinaryExpression:
Expand Down Expand Up @@ -8468,6 +8561,20 @@ module ts {
node.kind === SyntaxKind.FunctionExpression;
}

function getTypePredicateParameterIndex(parameterList: NodeArray<ParameterDeclaration>, parameter: Identifier): number {
let index = -1;
if (parameterList) {
for (let i = 0; i < parameterList.length; i++) {
let param = parameterList[i];
if (param.name.kind === SyntaxKind.Identifier &&
(<Identifier>param.name).text === parameter.text) {

return i;
}
}
}
}

function checkSignatureDeclaration(node: SignatureDeclaration) {
// Grammar checking
if (node.kind === SyntaxKind.IndexSignature) {
Expand All @@ -8488,6 +8595,27 @@ module ts {
checkSourceElement(node.type);
}

if (node.typePredicate) {
let links = getNodeLinks(node.typePredicate);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 3, 2015

Instead of using NodeLinks here, you could call getSignatureFromDeclaration, and just check the typePredicate of the signature. All the information will be there.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 4, 2015

Author Owner

Fixed.

if (links.typePredicateParameterIndex === undefined) {
links.typePredicateParameterIndex = getTypePredicateParameterIndex(node.parameters, node.typePredicate.parameterName);
}
if (!links.typeFromTypePredicate) {
links.typeFromTypePredicate = getTypeFromTypeNode(node.typePredicate.type);
}
if (links.typePredicateParameterIndex >= 0) {
checkTypeAssignableTo(
links.typeFromTypePredicate,
getTypeAtLocation(node.parameters[links.typePredicateParameterIndex]),
node.typePredicate.type);
}
else if(node.typePredicate.parameterName) {

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

Space between if and paren

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Fixed.

error(node.typePredicate.parameterName,
Diagnostics.Cannot_find_parameter_0,
node.typePredicate.parameterName.text);
}
}

if (produceDiagnostics) {
checkCollisionWithArgumentsInGeneratedCode(node);
if (compilerOptions.noImplicitAny && !node.type) {
Expand Down Expand Up @@ -10047,9 +10175,6 @@ module ts {
if (node.expression) {
let func = getContainingFunction(node);
if (func) {
let returnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func));
let exprType = checkExpressionCached(node.expression);

if (func.asteriskToken) {
// A generator does not need its return expressions checked against its return type.
// Instead, the yield expressions are checked against the element type.
Expand All @@ -10058,6 +10183,13 @@ module ts {
return;
}

let signature = getSignatureFromDeclaration(func);
let exprType = checkExpressionCached(node.expression);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

Is this an optimization? exprType at least needs to be moved back up. Even in a generator the expression needs to be type checked.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Fixed. It was just some rebase issues I had. I didn't know where to put it.

if (signature.typePredicate && exprType !== booleanType) {
error(node.expression, Diagnostics.A_type_guard_function_can_only_return_a_boolean);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

You should not have to do this check. It should be automatic by virtue of the assignability check a few lines below. Because in getSignatureFromDeclaration you specified booleanType as the return type.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Fixed.

}
let returnType = getReturnTypeOfSignature(signature);

if (func.kind === SyntaxKind.SetAccessor) {
error(node.expression, Diagnostics.Setters_cannot_return_a_value);
}
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ module ts {
Generators_are_not_allowed_in_an_ambient_context: { code: 1221, category: DiagnosticCategory.Error, key: "Generators are not allowed in an ambient context." },
An_overload_signature_cannot_be_declared_as_a_generator: { code: 1222, category: DiagnosticCategory.Error, key: "An overload signature cannot be declared as a generator." },
_0_tag_already_specified: { code: 1223, category: DiagnosticCategory.Error, key: "'{0}' tag already specified." },
A_non_type_guard_function_is_not_assignable_to_a_type_guard_function: { code: 1224, category: DiagnosticCategory.Error, key: "A non-type guard function is not assignable to a type guard function." },
A_type_guard_function_can_only_return_a_boolean: { code: 1225, category: DiagnosticCategory.Error, key: "A type-guard function can only return a boolean." },
Cannot_find_parameter_0: { code: 1226, category: DiagnosticCategory.Error, key: "Cannot find parameter '{0}'." },
Type_guard_annotation_0_is_not_assignable_to_1: { code: 1227, category: DiagnosticCategory.Error, key: "Type-guard annotation '{0}' is not assignable to '{1}'." },
Parameter_index_from_0_does_not_match_the_parameter_index_from_1: { code: 1228, category: DiagnosticCategory.Error, key: "Parameter index from '{0}' does not match the parameter index from '{1}'." },
Duplicate_identifier_0: { code: 2300, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." },
Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor: { code: 2301, category: DiagnosticCategory.Error, key: "Initializer of instance member variable '{0}' cannot reference identifier '{1}' declared in the constructor." },
Static_members_cannot_reference_class_type_parameters: { code: 2302, category: DiagnosticCategory.Error, key: "Static members cannot reference class type parameters." },
Expand Down
21 changes: 21 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,27 @@
"category": "Error",
"code": 1223
},
"A non-type guard function is not assignable to a type guard function.": {
"category": "Error",
"code": 1224
},
"A type-guard function can only return a boolean.": {
"category": "Error",
"code": 1225
},
"Cannot find parameter '{0}'.": {
"category": "Error",
"code": 1226
},
"Type-guard annotation '{0}' is not assignable to '{1}'.": {

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

"Type predicate" rather than "type guard annotation"

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Fixed.

"category": "Error",
"code": 1227
},
"Parameter index from '{0}' does not match the parameter index from '{1}'.": {

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 2, 2015

I would say "Parameter {0} is not in the same position as parameter {1}.

This comment has been minimized.

Copy link
@tinganho

tinganho Jun 3, 2015

Author Owner

Fixed.

"category": "Error",
"code": 1228
},


"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
Loading

0 comments on commit 373a776

Please sign in to comment.