From be75fe90afb004e2c8c873123fba0782cb2e471a Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Wed, 18 Jul 2018 10:55:54 -0700 Subject: [PATCH] Support implicit argument coercions for compound expressions with multiple overloads. Support string->formatted coercion (currently unused). --- .../expression/compound_expression.js | 53 ++++++++++++------- .../expression/definitions/coercion.js | 10 ++++ src/style-spec/expression/parsing_context.js | 4 ++ 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/style-spec/expression/compound_expression.js b/src/style-spec/expression/compound_expression.js index e5417f1c6bf..d9dd2b7223d 100644 --- a/src/style-spec/expression/compound_expression.js +++ b/src/style-spec/expression/compound_expression.js @@ -67,22 +67,6 @@ class CompoundExpression implements Expression { signature.length === args.length - 1 // correct param count )); - // First parse all the args - const parsedArgs: Array = []; - for (let i = 1; i < args.length; i++) { - const arg = args[i]; - let expected; - if (overloads.length === 1) { - const params = overloads[0][0]; - expected = Array.isArray(params) ? - params[i - 1] : - params.type; - } - const parsed = context.parse(arg, 1 + parsedArgs.length, expected); - if (!parsed) return null; - parsedArgs.push(parsed); - } - let signatureContext: ParsingContext = (null: any); for (const [params, evaluate] of overloads) { @@ -90,6 +74,29 @@ class CompoundExpression implements Expression { // we eventually succeed, we haven't polluted `context.errors`. signatureContext = new ParsingContext(context.registry, context.path, null, context.scope); + // First parse all the args, potentially coercing to the + // types expected by this overload. + const parsedArgs: Array = []; + let argParseFailed = false; + for (let i = 1; i < args.length; i++) { + const arg = args[i]; + const expectedType = Array.isArray(params) ? + params[i - 1] : + params.type; + + const parsed = signatureContext.parse(arg, 1 + parsedArgs.length, expectedType); + if (!parsed) { + argParseFailed = true; + break; + } + parsedArgs.push(parsed); + } + if (argParseFailed) { + // Couldn't coerce args of this overload to expected type, move + // on to next one. + continue; + } + if (Array.isArray(params)) { if (params.length !== parsedArgs.length) { signatureContext.error(`Expected ${params.length} arguments, but found ${parsedArgs.length} instead.`); @@ -117,10 +124,16 @@ class CompoundExpression implements Expression { const signatures = expected .map(([params]) => stringifySignature(params)) .join(' | '); - const actualTypes = parsedArgs - .map(arg => toString(arg.type)) - .join(', '); - context.error(`Expected arguments of type ${signatures}, but found (${actualTypes}) instead.`); + + const actualTypes = []; + // For error message, re-parse arguments without trying to + // apply any coercions + for (let i = 1; i < args.length; i++) { + const parsed = context.parse(args[i], 1 + actualTypes.length); + if (!parsed) return null; + actualTypes.push(toString(parsed.type)); + } + context.error(`Expected arguments of type ${signatures}, but found (${actualTypes.join(', ')}) instead.`); } return null; diff --git a/src/style-spec/expression/definitions/coercion.js b/src/style-spec/expression/definitions/coercion.js index ba50f63aee4..59fcbef4e2b 100644 --- a/src/style-spec/expression/definitions/coercion.js +++ b/src/style-spec/expression/definitions/coercion.js @@ -10,6 +10,7 @@ import type { Expression } from '../expression'; import type ParsingContext from '../parsing_context'; import type EvaluationContext from '../evaluation_context'; import type { Type } from '../types'; +import { Formatted, FormattedSection } from './formatted'; const types = { 'to-number': NumberType, @@ -73,6 +74,15 @@ class Coercion implements Expression { } } throw new RuntimeError(error || `Could not parse color from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`); + } else if (this.type.kind === 'formatted') { + let input; + for (const arg of this.args) { + input = arg.evaluate(ctx); + if (typeof input === 'string') { + return new Formatted([new FormattedSection(input, null, null)]); + } + } + throw new RuntimeError(`Could not parse formatted text from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`); } else { let value = null; for (const arg of this.args) { diff --git a/src/style-spec/expression/parsing_context.js b/src/style-spec/expression/parsing_context.js index 1ce14898323..5e4d1a51e21 100644 --- a/src/style-spec/expression/parsing_context.js +++ b/src/style-spec/expression/parsing_context.js @@ -113,6 +113,10 @@ class ParsingContext { if (!options.omitTypeAnnotations) { parsed = new Coercion(expected, [parsed]); } + } else if (expected.kind === 'formatted' && (actual.kind === 'value' || actual.kind === 'string')) { + if (!options.omitTypeAnnotations) { + parsed = new Coercion(expected, [parsed]); + } } else if (this.checkSubtype(this.expectedType, parsed.type)) { return null; }