From f756c5e71d1fba558a427c10c7e294c49376d081 Mon Sep 17 00:00:00 2001 From: Stephen Adams Date: Wed, 10 Jan 2018 12:12:25 -0800 Subject: [PATCH] Print names for generic method type parameters in function type - Added JS_GET_FLAG('STRONG_MODE') allow the code to be tailored to the type system version. - Split type stringifying code into V1 (existing logic) and V2 (strong mode Dart 2.0 logic). The V1 code will eventually be deleted. Change-Id: I78db91796040b6ecfa1bd5d6a9d64bd4c2bc4bcf Reviewed-on: https://dart-review.googlesource.com/33680 Commit-Queue: Stephen Adams Reviewed-by: Johnni Winther --- pkg/compiler/lib/src/ssa/graph_builder.dart | 2 + sdk/lib/_internal/js_runtime/lib/js_rti.dart | 254 +++++++++++++++--- .../dart2js/mirrors/mirrors_used_test.dart | 2 +- 3 files changed, 227 insertions(+), 31 deletions(-) diff --git a/pkg/compiler/lib/src/ssa/graph_builder.dart b/pkg/compiler/lib/src/ssa/graph_builder.dart index d1f23c763ec..9abf34d5a1a 100644 --- a/pkg/compiler/lib/src/ssa/graph_builder.dart +++ b/pkg/compiler/lib/src/ssa/graph_builder.dart @@ -299,6 +299,8 @@ abstract class GraphBuilder { return options.useContentSecurityPolicy; case 'IS_FULL_EMITTER': return !options.useStartupEmitter; + case 'STRONG_MODE': + return options.strongMode; default: return null; } diff --git a/sdk/lib/_internal/js_runtime/lib/js_rti.dart b/sdk/lib/_internal/js_runtime/lib/js_rti.dart index 64e0a4d764b..e7d9bef94ed 100644 --- a/sdk/lib/_internal/js_runtime/lib/js_rti.dart +++ b/sdk/lib/_internal/js_runtime/lib/js_rti.dart @@ -84,15 +84,15 @@ class TypeVariable { getMangledTypeName(TypeImpl type) => type._typeName; -/** - * Sets the runtime type information on [target]. [rti] is a type - * representation of type 4 or 5, that is, either a JavaScript array or - * `null`. - */ +/// Sets the runtime type information on [target]. [rti] is a type +/// representation of type 4 or 5, that is, either a JavaScript array or `null`. +/// +/// Called from generated code. +/// +/// This is used only for marking JavaScript Arrays (JSArray) with the element +/// type. // Don't inline. Let the JS engine inline this. The call expression is much // more compact that the inlined expansion. -// TODO(sra): For most objects it would be better to initialize the type info as -// a field in the constructor: http://dartbug.com/22676 . @NoInline() Object setRuntimeTypeInfo(Object target, var rti) { assert(rti == null || isJsArray(rti)); @@ -101,10 +101,10 @@ Object setRuntimeTypeInfo(Object target, var rti) { return target; } -/** - * Returns the runtime type information of [target]. The returned value is a - * list of type representations for the type arguments. - */ +/// Returns the runtime type information of [target]. The returned value is a +/// list of type representations for the type arguments. +/// +/// Called from generated code. getRuntimeTypeInfo(Object target) { if (target == null) return null; String rtiName = JS_GET_NAME(JsGetName.RTI_NAME); @@ -120,10 +120,10 @@ getRuntimeTypeArguments(target, substitutionName) { return substitute(substitution, getRuntimeTypeInfo(target)); } -/** - * Returns the [index]th type argument of [target] as an instance of - * [substitutionName]. - */ +/// Returns the [index]th type argument of [target] as an instance of +/// [substitutionName]. +/// +/// Called from generated code. @NoThrows() @NoSideEffects() @NoInline() @@ -132,6 +132,9 @@ getRuntimeTypeArgument(Object target, String substitutionName, int index) { return arguments == null ? null : getIndex(arguments, index); } +/// Returns the [index]th type argument of [target]. +/// +/// Called from generated code. @NoThrows() @NoSideEffects() @NoInline() @@ -153,22 +156,37 @@ String getClassName(var object) { * of type 4, the JavaScript array, where the first element represents the class * and the remaining elements represent the type arguments. */ -String getRuntimeTypeAsString(var rti, {String onTypeVariable(int i)}) { +String _getRuntimeTypeAsStringV1(var rti, {String onTypeVariable(int i)}) { assert(isJsArray(rti)); String className = rawRtiToJsConstructorName(getIndex(rti, 0)); - return '$className${joinArguments(rti, 1, onTypeVariable: onTypeVariable)}'; + return '$className${joinArgumentsV1(rti, 1, onTypeVariable: onTypeVariable)}'; } -/** - * Returns a human-readable representation of the type representation [rti]. - */ +String _getRuntimeTypeAsStringV2(var rti, List genericContext) { + assert(isJsArray(rti)); + String className = rawRtiToJsConstructorName(getIndex(rti, 0)); + return '$className${joinArgumentsV2(rti, 1, genericContext)}'; +} + +/// Returns a human-readable representation of the type representation [rti]. +/// +/// Called from generated code. +/// +/// [onTypeVariable] is used only from dart:mirrors. +@NoInline() String runtimeTypeToString(var rti, {String onTypeVariable(int i)}) { + return JS_GET_FLAG('STRONG_MODE') + ? runtimeTypeToStringV2(rti, null) + : runtimeTypeToStringV1(rti, onTypeVariable: onTypeVariable); +} + +String runtimeTypeToStringV1(var rti, {String onTypeVariable(int i)}) { if (rti == null) { return 'dynamic'; } if (isJsArray(rti)) { // A list representing a type with arguments. - return getRuntimeTypeAsString(rti, onTypeVariable: onTypeVariable); + return _getRuntimeTypeAsStringV1(rti, onTypeVariable: onTypeVariable); } if (isJsFunction(rti)) { // A reference to the constructor. @@ -185,15 +203,43 @@ String runtimeTypeToString(var rti, {String onTypeVariable(int i)}) { String typedefPropertyName = JS_GET_NAME(JsGetName.TYPEDEF_TAG); var typedefInfo = JS('', '#[#]', rti, typedefPropertyName); if (typedefInfo != null) { - return runtimeTypeToString(typedefInfo, onTypeVariable: onTypeVariable); + return runtimeTypeToStringV1(typedefInfo, onTypeVariable: onTypeVariable); } - return _functionRtiToString(rti, onTypeVariable); + return _functionRtiToStringV1(rti, onTypeVariable); } // We should not get here. return 'unknown-reified-type'; } -String _functionRtiToString(var rti, String onTypeVariable(int i)) { +String runtimeTypeToStringV2(var rti, List genericContext) { + if (rti == null) { + return 'dynamic'; + } + if (isJsArray(rti)) { + // A list representing a type with arguments. + return _getRuntimeTypeAsStringV2(rti, genericContext); + } + if (isJsFunction(rti)) { + // A reference to the constructor. + return rawRtiToJsConstructorName(rti); + } + if (isGenericFunctionTypeParameter(rti)) { + int index = rti; + if (genericContext == null || index < 0 || index >= genericContext.length) { + return 'unexpected-generic-index:${index}'; + } + return '${genericContext[genericContext.length - index - 1]}'; + } + String functionPropertyName = JS_GET_NAME(JsGetName.FUNCTION_TYPE_TAG); + if (JS('bool', 'typeof #[#] != "undefined"', rti, functionPropertyName)) { + // TODO(sra): If there is a typedef tag, use the typedef name. + return _functionRtiToStringV2(rti, genericContext); + } + // We should not get here. + return 'unknown-reified-type'; +} + +String _functionRtiToStringV1(var rti, String onTypeVariable(int i)) { String returnTypeText; String voidTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_VOID_RETURN_TAG); if (JS('bool', '!!#[#]', rti, voidTag)) { @@ -202,7 +248,7 @@ String _functionRtiToString(var rti, String onTypeVariable(int i)) { String returnTypeTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_RETURN_TYPE_TAG); var returnRti = JS('', '#[#]', rti, returnTypeTag); returnTypeText = - runtimeTypeToString(returnRti, onTypeVariable: onTypeVariable); + runtimeTypeToStringV1(returnRti, onTypeVariable: onTypeVariable); } String argumentsText = ''; @@ -216,7 +262,7 @@ String _functionRtiToString(var rti, String onTypeVariable(int i)) { for (var argument in arguments) { argumentsText += sep; argumentsText += - runtimeTypeToString(argument, onTypeVariable: onTypeVariable); + runtimeTypeToStringV1(argument, onTypeVariable: onTypeVariable); sep = ', '; } } @@ -231,7 +277,7 @@ String _functionRtiToString(var rti, String onTypeVariable(int i)) { for (var argument in optionalArguments) { argumentsText += sep; argumentsText += - runtimeTypeToString(argument, onTypeVariable: onTypeVariable); + runtimeTypeToStringV1(argument, onTypeVariable: onTypeVariable); sep = ', '; } argumentsText += ']'; @@ -246,7 +292,8 @@ String _functionRtiToString(var rti, String onTypeVariable(int i)) { sep = ''; for (String name in extractKeys(namedArguments)) { argumentsText += sep; - argumentsText += runtimeTypeToString(JS('', '#[#]', namedArguments, name), + argumentsText += runtimeTypeToStringV1( + JS('', '#[#]', namedArguments, name), onTypeVariable: onTypeVariable); argumentsText += ' $name'; sep = ', '; @@ -261,12 +308,129 @@ String _functionRtiToString(var rti, String onTypeVariable(int i)) { return '(${argumentsText}) => ${returnTypeText}'; } +// Returns a formatted String version of a function type. +// +// [genericContext] is list of the names of generic type parameters for generic +// function types. The de Bruijn indexing scheme references the type variables +// from the inner scope out. The parameters for each scope are pushed in +// reverse, e.g. `((R))` creates the list `[Q,P,T,S,R]`. This +// allows the de Bruijn index to simply index backwards from the end of +// [genericContext], e.g. in the outer scope index `0` is P and `1` is Q, and in +// the inner scope index `0` is R, `3` is P, and `4` is Q. +// +// [genericContext] is initially `null`. +String _functionRtiToStringV2(var rti, List genericContext) { + String typeParameters = ''; + int outerContextLength; + + String boundsTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_GENERIC_BOUNDS_TAG); + if (hasField(rti, boundsTag)) { + List boundsRti = JS('JSFixedArray', '#[#]', rti, boundsTag); + if (genericContext == null) { + genericContext = []; + } else { + outerContextLength = genericContext.length; + } + int offset = genericContext.length; + for (int i = boundsRti.length; i > 0; i--) { + genericContext.add('T${offset + i}'); + } + // All variables are in scope in the bounds. + String typeSep = ''; + typeParameters = '<'; + for (int i = 0; i < boundsRti.length; i++) { + typeParameters += typeSep; + typeParameters += genericContext[genericContext.length - i - 1]; + typeSep = ', '; + var boundRti = boundsRti[i]; + if (isInterestingBound(boundRti)) { + typeParameters += + ' extends ' + runtimeTypeToStringV2(boundRti, genericContext); + } + } + typeParameters += '>'; + } + + String returnTypeText; + String voidTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_VOID_RETURN_TAG); + if (JS('bool', '!!#[#]', rti, voidTag)) { + returnTypeText = 'void'; + } else { + String returnTypeTag = JS_GET_NAME(JsGetName.FUNCTION_TYPE_RETURN_TYPE_TAG); + var returnRti = JS('', '#[#]', rti, returnTypeTag); + returnTypeText = runtimeTypeToStringV2(returnRti, genericContext); + } + + String argumentsText = ''; + String sep = ''; + + String requiredParamsTag = + JS_GET_NAME(JsGetName.FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG); + if (hasField(rti, requiredParamsTag)) { + List arguments = JS('JSFixedArray', '#[#]', rti, requiredParamsTag); + for (var argument in arguments) { + argumentsText += sep; + argumentsText += runtimeTypeToStringV2(argument, genericContext); + sep = ', '; + } + } + + String optionalParamsTag = + JS_GET_NAME(JsGetName.FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG); + bool hasOptionalArguments = JS('bool', '# in #', optionalParamsTag, rti); + if (hasOptionalArguments) { + List optionalArguments = JS('JSFixedArray', '#[#]', rti, optionalParamsTag); + argumentsText += '$sep['; + sep = ''; + for (var argument in optionalArguments) { + argumentsText += sep; + argumentsText += runtimeTypeToStringV2(argument, genericContext); + sep = ', '; + } + argumentsText += ']'; + } + + String namedParamsTag = + JS_GET_NAME(JsGetName.FUNCTION_TYPE_NAMED_PARAMETERS_TAG); + bool hasNamedArguments = JS('bool', '# in #', namedParamsTag, rti); + if (hasNamedArguments) { + var namedArguments = JS('', '#[#]', rti, namedParamsTag); + argumentsText += '$sep{'; + sep = ''; + for (String name in extractKeys(namedArguments)) { + argumentsText += sep; + argumentsText += runtimeTypeToStringV2( + JS('', '#[#]', namedArguments, name), genericContext); + argumentsText += ' $name'; + sep = ', '; + } + argumentsText += '}'; + } + + if (outerContextLength != null) { + // Pop all of the generic type parameters. + JS('', '#.length = #', genericContext, outerContextLength); + } + + // TODO(sra): Below is the same format as the VM. Change to: + // + // return '${returnTypeText} Function${typeParameters}(${argumentsText})'; + // + return '${typeParameters}(${argumentsText}) => ${returnTypeText}'; +} + /** * Creates a comma-separated string of human-readable representations of the * type representations in the JavaScript array [types] starting at index * [startIndex]. */ -String joinArguments(var types, int startIndex, +String joinArguments(var types, int startIndex) { + return JS_GET_FLAG('STRONG_MODE') + ? joinArgumentsV2(types, startIndex, null) + : joinArgumentsV1(types, startIndex); +} + +String joinArgumentsV1(var types, int startIndex, {String onTypeVariable(int i)}) { if (types == null) return ''; assert(isJsArray(types)); @@ -283,7 +447,29 @@ String joinArguments(var types, int startIndex, if (argument != null) { allDynamic = false; } - buffer.write(runtimeTypeToString(argument, onTypeVariable: onTypeVariable)); + buffer + .write(runtimeTypeToStringV1(argument, onTypeVariable: onTypeVariable)); + } + return allDynamic ? '' : '<$buffer>'; +} + +String joinArgumentsV2(var types, int startIndex, List genericContext) { + if (types == null) return ''; + assert(isJsArray(types)); + bool firstArgument = true; + bool allDynamic = true; + StringBuffer buffer = new StringBuffer(''); + for (int index = startIndex; index < getLength(types); index++) { + if (firstArgument) { + firstArgument = false; + } else { + buffer.write(', '); + } + var argument = getIndex(types, index); + if (argument != null) { + allDynamic = false; + } + buffer.write(runtimeTypeToStringV2(argument, genericContext)); } return allDynamic ? '' : '<$buffer>'; } @@ -811,3 +997,11 @@ bool isIdentical(var s, var t) => JS('bool', '# === #', s, t); * `null` and `undefined` (which we can avoid). */ bool isNotIdentical(var s, var t) => JS('bool', '# !== #', s, t); + +/// 'Top' bounds are uninteresting: null/undefined and Object. +bool isInterestingBound(rti) => + rti != null && + isNotIdentical( + rti, + JS_BUILTIN( + 'depends:none;effects:none;', JsBuiltin.dartObjectConstructor)); diff --git a/tests/compiler/dart2js/mirrors/mirrors_used_test.dart b/tests/compiler/dart2js/mirrors/mirrors_used_test.dart index f82d85d4ff1..6aa3ca81287 100644 --- a/tests/compiler/dart2js/mirrors/mirrors_used_test.dart +++ b/tests/compiler/dart2js/mirrors/mirrors_used_test.dart @@ -70,7 +70,7 @@ void main() { // 2. Some code was refactored, and there are more methods. // Either situation could be problematic, but in situation 2, it is often // acceptable to increase [expectedMethodCount] a little. - int expectedMethodCount = 477; + int expectedMethodCount = 478; Expect.isTrue( generatedCode.length <= expectedMethodCount, 'Too many compiled methods: '