PageRenderTime 51ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/Runtime/Microsoft.Dynamic/Actions/Calls/TypeInferer.cs

http://github.com/IronLanguages/main
C# | 605 lines | 380 code | 84 blank | 141 comment | 108 complexity | 0fe578f6e184acc34cb5c89ab5d9f191 MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception
  1. /* ****************************************************************************
  2. *
  3. * Copyright (c) Microsoft Corporation.
  4. *
  5. * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
  6. * copy of the license can be found in the License.html file at the root of this distribution. If
  7. * you cannot locate the Apache License, Version 2.0, please send an email to
  8. * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9. * by the terms of the Apache License, Version 2.0.
  10. *
  11. * You must not remove this notice, or any other, from this software.
  12. *
  13. *
  14. * ***************************************************************************/
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Diagnostics;
  18. using System.Dynamic;
  19. using System.Reflection;
  20. using System.Runtime.CompilerServices;
  21. using Microsoft.Scripting.Generation;
  22. using Microsoft.Scripting.Runtime;
  23. using Microsoft.Scripting.Utils;
  24. namespace Microsoft.Scripting.Actions.Calls {
  25. public static class TypeInferer {
  26. private static ArgumentInputs EnsureInputs(Dictionary<Type, ArgumentInputs> dict, Type type) {
  27. ArgumentInputs res;
  28. if (!dict.TryGetValue(type, out res)) {
  29. dict[type] = res = new ArgumentInputs(type);
  30. }
  31. return res;
  32. }
  33. internal static MethodCandidate InferGenericMethod(ApplicableCandidate/*!*/ candidate, ActualArguments/*!*/ actualArgs) {
  34. OverloadInfo target = candidate.Method.Overload;
  35. Assert.NotNull(target);
  36. Debug.Assert(target.IsGenericMethodDefinition);
  37. Debug.Assert(target.IsGenericMethod && target.ContainsGenericParameters);
  38. List<DynamicMetaObject/*!*/> args = GetAllArguments(candidate, actualArgs);
  39. if (args == null) {
  40. return null;
  41. }
  42. Dictionary<Type, List<Type>> dependencies = GetDependencyMapping(target);
  43. Type[] genArgs = GetSortedGenericArguments(target, dependencies);
  44. Dictionary<Type, ArgumentInputs> inputs = GetArgumentToInputMapping(candidate.Method, args);
  45. // now process the inputs
  46. var binding = new Dictionary<Type, Type>();
  47. var restrictions = new Dictionary<DynamicMetaObject, BindingRestrictions>();
  48. bool noMethod = false;
  49. foreach (Type t in genArgs) {
  50. ArgumentInputs inps;
  51. if (!inputs.TryGetValue(t, out inps)) {
  52. continue;
  53. }
  54. Type bestType = inps.GetBestType(candidate.Method.Resolver, binding, restrictions);
  55. if (bestType == null) {
  56. // we conflict with possible constraints
  57. noMethod = true;
  58. break;
  59. }
  60. }
  61. if (!noMethod) {
  62. // finally build a new MethodCandidate for the generic method
  63. genArgs = GetGenericArgumentsForInferedMethod(target, binding);
  64. if (genArgs == null) {
  65. // not all types we're inferred
  66. return null;
  67. }
  68. OverloadInfo newMethod = target.MakeGenericMethod(genArgs);
  69. List<ParameterWrapper> newWrappers = CreateNewWrappers(candidate.Method, newMethod, target);
  70. List<ArgBuilder> argBuilders = CreateNewArgBuilders(candidate.Method, newMethod);
  71. if (argBuilders == null) {
  72. // one or more arg builders don't support type inference
  73. return null;
  74. }
  75. if (restrictions.Count == 0) {
  76. restrictions = null;
  77. }
  78. // create the new method candidate
  79. return candidate.Method.ReplaceMethod(newMethod, newWrappers, argBuilders, restrictions);
  80. }
  81. return null;
  82. }
  83. /// <summary>
  84. /// Gets the generic arguments for method based upon the constraints discovered during
  85. /// type inference. Returns null if not all generic arguments had their types inferred.
  86. /// </summary>
  87. private static Type[] GetGenericArgumentsForInferedMethod(OverloadInfo target, Dictionary<Type, Type> constraints) {
  88. Type[] genArgs = ArrayUtils.MakeArray(target.GenericArguments);
  89. for (int i = 0; i < genArgs.Length; i++) {
  90. Type newType;
  91. if (!constraints.TryGetValue(genArgs[i], out newType)) {
  92. // we didn't discover any types for this type argument
  93. return null;
  94. }
  95. genArgs[i] = newType;
  96. }
  97. return genArgs;
  98. }
  99. /// <summary>
  100. /// Creates a new set of arg builders for the given generic method definition which target the new
  101. /// parameters.
  102. /// </summary>
  103. private static List<ArgBuilder> CreateNewArgBuilders(MethodCandidate candidate, OverloadInfo newOverload) {
  104. List<ArgBuilder> argBuilders = new List<ArgBuilder>();
  105. foreach (ArgBuilder oldArgBuilder in candidate.ArgBuilders) {
  106. var pi = oldArgBuilder.ParameterInfo;
  107. if (pi != null && (pi.ParameterType.IsGenericParameter || pi.ParameterType.ContainsGenericParameters())) {
  108. ArgBuilder replacement = oldArgBuilder.Clone(newOverload.Parameters[pi.Position]);
  109. if (replacement == null) {
  110. return null;
  111. }
  112. argBuilders.Add(replacement);
  113. } else {
  114. argBuilders.Add(oldArgBuilder);
  115. }
  116. }
  117. return argBuilders;
  118. }
  119. /// <summary>
  120. /// Creates a new list of ParameterWrappers for the generic method replacing the old parameters with the new ones.
  121. /// </summary>
  122. private static List<ParameterWrapper> CreateNewWrappers(MethodCandidate candidate, OverloadInfo newOverload, OverloadInfo oldOverload) {
  123. List<ParameterWrapper> newWrappers = new List<ParameterWrapper>();
  124. for (int i = 0; i < candidate.ParameterCount; i++) {
  125. ParameterWrapper oldWrap = candidate.GetParameter(i);
  126. ParameterInfo pi = null;
  127. Type newType = oldWrap.Type;
  128. if (oldWrap.ParameterInfo != null) {
  129. pi = newOverload.Parameters[oldWrap.ParameterInfo.Position];
  130. ParameterInfo oldParam = oldOverload.Parameters[oldWrap.ParameterInfo.Position];
  131. if (oldParam.ParameterType == oldWrap.Type) {
  132. newType = pi.ParameterType;
  133. } else if (pi.ParameterType.IsByRef) {
  134. newType = pi.ParameterType.GetElementType();
  135. if (oldParam.ParameterType.GetElementType() != oldWrap.Type) {
  136. Debug.Assert(CompilerHelpers.IsStrongBox(oldWrap.Type));
  137. newType = typeof(StrongBox<>).MakeGenericType(newType);
  138. }
  139. } else {
  140. Debug.Assert(oldParam.ParameterType.GetElementType() == oldWrap.Type);
  141. newType = pi.ParameterType.GetElementType();
  142. }
  143. }
  144. newWrappers.Add(new ParameterWrapper(pi, newType, oldWrap.Name, oldWrap.Flags));
  145. }
  146. return newWrappers;
  147. }
  148. private static List<DynamicMetaObject> GetAllArguments(ApplicableCandidate candidate, ActualArguments actualArgs) {
  149. List<DynamicMetaObject> args = new List<DynamicMetaObject>();
  150. for (int i = 0; i < actualArgs.Count; i++) {
  151. int index = candidate.ArgumentBinding.ArgumentToParameter(i);
  152. if (index < actualArgs.Arguments.Count) {
  153. args.Add(actualArgs.Arguments[index]);
  154. } else {
  155. args.Add(actualArgs.NamedArguments[index - actualArgs.Arguments.Count]);
  156. }
  157. }
  158. return args;
  159. }
  160. /// <summary>
  161. /// Gets the generic type arguments sorted so that the type arguments
  162. /// that are depended upon by other type arguments are sorted before
  163. /// their dependencies.
  164. /// </summary>
  165. private static Type[] GetSortedGenericArguments(OverloadInfo info, Dictionary<Type, List<Type>> dependencies) {
  166. Type[] genArgs = ArrayUtils.MakeArray(info.GenericArguments);
  167. // Then sort the arguments based upon those dependencies
  168. Array.Sort(genArgs, (x, y) => {
  169. if (Object.ReferenceEquals(x, y)) {
  170. return 0;
  171. }
  172. bool isDependent = IsDependentConstraint(dependencies, x, y);
  173. if (isDependent) {
  174. return 1;
  175. }
  176. isDependent = IsDependentConstraint(dependencies, y, x);
  177. if (isDependent) {
  178. return -1;
  179. }
  180. int xhash = x.GetHashCode(), yhash = y.GetHashCode();
  181. if (xhash != yhash) {
  182. return xhash - yhash;
  183. }
  184. long idDiff = IdDispenser.GetId(x) - IdDispenser.GetId(y);
  185. return idDiff > 0 ? 1 : -1;
  186. });
  187. return genArgs;
  188. }
  189. /// <summary>
  190. /// Checks to see if the x type parameter is dependent upon the y type parameter.
  191. /// </summary>
  192. private static bool IsDependentConstraint(Dictionary<Type, List<Type>> dependencies, Type x, Type y) {
  193. List<Type> childDeps;
  194. if (dependencies.TryGetValue(x, out childDeps)) {
  195. foreach (Type t in childDeps) {
  196. if (t == y) {
  197. return true;
  198. }
  199. bool isDependent = IsDependentConstraint(dependencies, t, y);
  200. if (isDependent) {
  201. return true;
  202. }
  203. }
  204. }
  205. return false;
  206. }
  207. /// <summary>
  208. /// Builds a mapping based upon generic parameter constraints between related generic
  209. /// parameters. This is then used to sort the generic parameters so that we can process
  210. /// the least dependent parameters first. For example given the method:
  211. ///
  212. /// void Foo{T0, T1}(T0 x, T1 y) where T0 : T1
  213. ///
  214. /// We need to first infer the type information for T1 before we infer the type information
  215. /// for T0 so that we can ensure the constraints are correct.
  216. /// </summary>
  217. private static Dictionary<Type, List<Type>> GetDependencyMapping(OverloadInfo info) {
  218. Dictionary<Type, List<Type>> dependencies = new Dictionary<Type, List<Type>>();
  219. // need to calculate any dependencies between parameters.
  220. foreach (Type genArg in info.GenericArguments) {
  221. Type[] constraints = genArg.GetGenericParameterConstraints();
  222. foreach (Type t in constraints) {
  223. if (t.IsGenericParameter) {
  224. AddDependency(dependencies, genArg, t);
  225. } else if (t.ContainsGenericParameters()) {
  226. AddNestedDependencies(dependencies, genArg, t);
  227. }
  228. }
  229. }
  230. return dependencies;
  231. }
  232. private static void AddNestedDependencies(Dictionary<Type, List<Type>> dependencies, Type genArg, Type t) {
  233. Type[] innerArgs = t.GetGenericArguments();
  234. foreach (Type innerArg in innerArgs) {
  235. if (innerArg.IsGenericParameter) {
  236. AddDependency(dependencies, genArg, innerArg);
  237. } else if (innerArg.ContainsGenericParameters()) {
  238. AddNestedDependencies(dependencies, genArg, innerArg);
  239. }
  240. }
  241. }
  242. private static void AddDependency(Dictionary<Type, List<Type>> dependencies, Type genArg, Type t) {
  243. List<Type> deps;
  244. if (!dependencies.TryGetValue(genArg, out deps)) {
  245. dependencies[genArg] = deps = new List<Type>();
  246. }
  247. deps.Add(t);
  248. }
  249. /// <summary>
  250. /// Returns a mapping from generic type parameter to the input DMOs which map to it.
  251. /// </summary>
  252. private static Dictionary<Type/*!*/, ArgumentInputs/*!*/>/*!*/ GetArgumentToInputMapping(MethodCandidate/*!*/ candidate, IList<DynamicMetaObject/*!*/>/*!*/ args) {
  253. Dictionary<Type, ArgumentInputs> inputs = new Dictionary<Type, ArgumentInputs>();
  254. for (int curParam = 0; curParam < candidate.ParameterCount; curParam++) {
  255. ParameterWrapper param = candidate.GetParameter(curParam);
  256. if (param.IsParamsArray) {
  257. AddOneInput(inputs, args[curParam], param.Type.GetElementType());
  258. } else if (param.IsByRef) {
  259. AddOneInput(inputs, args[curParam], param.ParameterInfo.ParameterType);
  260. } else {
  261. AddOneInput(inputs, args[curParam], param.Type);
  262. }
  263. }
  264. return inputs;
  265. }
  266. /// <summary>
  267. /// Adds any additional ArgumentInputs entries for the given object and parameter type.
  268. /// </summary>
  269. private static void AddOneInput(Dictionary<Type, ArgumentInputs> inputs, DynamicMetaObject arg, Type paramType) {
  270. if (paramType.ContainsGenericParameters()) {
  271. List<Type> containedGenArgs = new List<Type>();
  272. CollectGenericParameters(paramType, containedGenArgs);
  273. foreach (Type type in containedGenArgs) {
  274. EnsureInputs(inputs, type).AddInput(arg, paramType);
  275. }
  276. }
  277. }
  278. /// <summary>
  279. /// Walks the nested generic hierarchy to construct all of the generic parameters referred
  280. /// to by this type. For example if getting the generic parameters for the x parameter on
  281. /// the method:
  282. ///
  283. /// void Foo{T0, T1}(Dictionary{T0, T1} x);
  284. ///
  285. /// We would add both typeof(T0) and typeof(T1) to the list of generic arguments.
  286. /// </summary>
  287. private static void CollectGenericParameters(Type type, List<Type> containedGenArgs) {
  288. if (type.IsGenericParameter) {
  289. if (!containedGenArgs.Contains(type)) {
  290. containedGenArgs.Add(type);
  291. }
  292. } else if (type.ContainsGenericParameters()) {
  293. if (type.IsArray || type.IsByRef) {
  294. CollectGenericParameters(type.GetElementType(), containedGenArgs);
  295. } else {
  296. Type[] genArgs = type.GetGenericArguments();
  297. for (int i = 0; i < genArgs.Length; i++) {
  298. CollectGenericParameters(genArgs[i], containedGenArgs);
  299. }
  300. }
  301. }
  302. }
  303. /// <summary>
  304. /// Maps a single type parameter to the possible parameters and DynamicMetaObjects
  305. /// we can get inference from. For example for the signature:
  306. ///
  307. /// void Foo{T0, T1}(T0 x, T1 y, IList{T1} z);
  308. ///
  309. /// We would have one ArgumentInput for T0 which holds onto the DMO providing the argument
  310. /// value for x. We would also have one ArgumentInput for T1 which holds onto the 2 DMOs
  311. /// for y and z. Associated with y would be a GenericParameterInferer and associated with
  312. /// z would be a ConstructedParameterInferer.
  313. /// </summary>
  314. class ArgumentInputs {
  315. private readonly List<Type>/*!*/ _parameterTypes = new List<Type>();
  316. private readonly List<DynamicMetaObject>/*!*/ _inputs = new List<DynamicMetaObject>();
  317. private readonly Type/*!*/ _genericParam;
  318. public ArgumentInputs(Type/*!*/ genericParam) {
  319. Assert.NotNull(genericParam);
  320. Debug.Assert(genericParam.IsGenericParameter);
  321. _genericParam = genericParam;
  322. }
  323. public void AddInput(DynamicMetaObject/*!*/ value, Type/*!*/ parameterType) {
  324. _parameterTypes.Add(parameterType);
  325. _inputs.Add(value);
  326. }
  327. public Type GetBestType(OverloadResolver/*!*/ resolver, Dictionary<Type, Type>/*!*/ binding, Dictionary<DynamicMetaObject, BindingRestrictions>/*!*/ restrictions) {
  328. Type curType = null;
  329. for (int i = 0; i < _parameterTypes.Count; i++) {
  330. Type nextType = GetInferedType(resolver, _genericParam, _parameterTypes[i], _inputs[i], binding, restrictions);
  331. if (nextType == null) {
  332. // no mapping available
  333. return null;
  334. } else if (curType == null || curType.IsAssignableFrom(nextType)) {
  335. curType = nextType;
  336. } else if (!nextType.IsAssignableFrom(curType)) {
  337. // inconsistent constraint.
  338. return null;
  339. } else {
  340. curType = nextType;
  341. }
  342. }
  343. return curType;
  344. }
  345. }
  346. /// <summary>
  347. /// Provides generic type inference for a single parameter.
  348. /// </summary>
  349. /// <remarks>
  350. /// For example:
  351. /// M{T}(T x)
  352. /// M{T}(IList{T} x)
  353. /// M{T}(ref T x)
  354. /// M{T}(T[] x)
  355. /// M{T}(ref Dictionary{T,T}[] x)
  356. /// </remarks>
  357. internal static Type GetInferedType(OverloadResolver/*!*/ resolver, Type/*!*/ genericParameter, Type/*!*/ parameterType,
  358. DynamicMetaObject/*!*/ input, Dictionary<Type, Type>/*!*/ binding, Dictionary<DynamicMetaObject, BindingRestrictions>/*!*/ restrictions) {
  359. if (parameterType.IsSubclassOf(typeof(Delegate))) {
  360. // see if we have an invokable object which can be used to infer into this delegate
  361. IInferableInvokable invokeInfer = input as IInferableInvokable;
  362. if (invokeInfer != null) {
  363. InferenceResult inference = invokeInfer.GetInferredType(parameterType, genericParameter);
  364. if (inference != null) {
  365. if (inference.Restrictions != BindingRestrictions.Empty) {
  366. restrictions[input] = inference.Restrictions;
  367. }
  368. binding[genericParameter] = inference.Type;
  369. if (ConstraintsViolated(inference.Type, genericParameter, binding)) {
  370. return null;
  371. }
  372. return inference.Type;
  373. }
  374. }
  375. }
  376. return GetInferedType(genericParameter, parameterType, resolver.GetGenericInferenceType(input), input.LimitType, binding);
  377. }
  378. /// <summary>
  379. /// Provides generic type inference for a single parameter.
  380. /// </summary>
  381. /// <remarks>
  382. /// For example:
  383. /// M{T}(T x)
  384. /// M{T}(IList{T} x)
  385. /// M{T}(ref T x)
  386. /// M{T}(T[] x)
  387. /// M{T}(ref Dictionary{T,T}[] x)
  388. /// </remarks>
  389. public static Type GetInferedType(Type/*!*/ genericParameter, Type/*!*/ parameterType, Type inputType, Type/*!*/ argType, Dictionary<Type, Type>/*!*/ binding) {
  390. Debug.Assert(genericParameter.IsGenericParameter);
  391. if (parameterType.IsGenericParameter) {
  392. if (inputType != null) {
  393. binding[genericParameter] = inputType;
  394. if (ConstraintsViolated(inputType, genericParameter, binding)) {
  395. return null;
  396. }
  397. }
  398. return inputType;
  399. }
  400. if (parameterType.IsInterface()) {
  401. return GetInferedTypeForInterface(genericParameter, parameterType, inputType, binding);
  402. }
  403. if (parameterType.IsArray) {
  404. return binding[genericParameter] = MatchGenericParameter(genericParameter, argType, parameterType, binding);
  405. }
  406. if (parameterType.IsByRef) {
  407. if (CompilerHelpers.IsStrongBox(argType)) {
  408. argType = argType.GetGenericArguments()[0];
  409. }
  410. return binding[genericParameter] = MatchGenericParameter(genericParameter, argType, parameterType.GetElementType(), binding);
  411. }
  412. // see if we're anywhere in our base class hierarchy
  413. Type genType = parameterType.GetGenericTypeDefinition();
  414. while (argType != typeof(object)) {
  415. if (argType.IsGenericType() && argType.GetGenericTypeDefinition() == genType) {
  416. // TODO: Merge w/ the interface logic?
  417. return binding[genericParameter] = MatchGenericParameter(genericParameter, argType, parameterType, binding);
  418. }
  419. argType = argType.GetBaseType();
  420. }
  421. return null;
  422. }
  423. //
  424. // The argument can implement multiple instantiations of the same generic interface definition, e.g.
  425. // ArgType : I<C<X>>, I<D<Y>>
  426. // ParamType == I<C<T>>
  427. //
  428. // Unless X == Y we can't infer T.
  429. //
  430. private static Type GetInferedTypeForInterface(Type/*!*/ genericParameter, Type/*!*/ interfaceType, Type inputType, Dictionary<Type, Type>/*!*/ binding) {
  431. Debug.Assert(interfaceType.IsInterface());
  432. Type match = null;
  433. Type genTypeDef = interfaceType.GetGenericTypeDefinition();
  434. foreach (Type ifaceType in inputType.GetInterfaces()) {
  435. if (ifaceType.IsGenericType() && ifaceType.GetGenericTypeDefinition() == genTypeDef) {
  436. if (!MatchGenericParameter(genericParameter, ifaceType, interfaceType, binding, ref match)) {
  437. return null;
  438. }
  439. }
  440. }
  441. binding[genericParameter] = match;
  442. return match;
  443. }
  444. /// <summary>
  445. /// Checks if the constraints are violated by the given input for the specified generic method parameter.
  446. ///
  447. /// This method must be supplied with a mapping for any dependent generic method type parameters which
  448. /// this one can be constrained to. For example for the signature "void Foo{T0, T1}(T0 x, T1 y) where T0 : T1".
  449. /// we cannot know if the constraints are violated unless we know what we have calculated T1 to be.
  450. /// </summary>
  451. private static bool ConstraintsViolated(Type inputType, Type genericMethodParameterType, Dictionary<Type, Type> binding) {
  452. return ReflectionUtils.ConstraintsViolated(genericMethodParameterType, inputType, binding, false);
  453. }
  454. private static Type MatchGenericParameter(Type genericParameter, Type closedType, Type openType, Dictionary<Type, Type> binding) {
  455. Type match = null;
  456. return MatchGenericParameter(genericParameter, closedType, openType, binding, ref match) ? match : null;
  457. }
  458. /// <summary>
  459. /// Finds all occurences of <c>genericParameter</c> in <c>openType</c> and the corresponding concrete types in <c>closedType</c>.
  460. /// Returns true iff all occurences of the generic parameter in the open type correspond to the same concrete type in the closed type
  461. /// and this type satisfies given <c>constraints</c>. Returns the concrete type in <c>match</c> if so.
  462. /// </summary>
  463. private static bool MatchGenericParameter(Type genericParameter, Type closedType, Type openType, Dictionary<Type, Type> binding, ref Type match) {
  464. Type m = match;
  465. bool result = ReflectionUtils.BindGenericParameters(openType, closedType, (parameter, type) => {
  466. if (parameter == genericParameter) {
  467. if (m != null) {
  468. return m == type;
  469. }
  470. if (ConstraintsViolated(type, genericParameter, binding)) {
  471. return false;
  472. }
  473. m = type;
  474. }
  475. return true;
  476. });
  477. match = m;
  478. return result;
  479. }
  480. }
  481. /// <summary>
  482. /// Implemented by DynamicMetaObject subclasses when the associated object
  483. /// can participate in generic method type inference. This interface
  484. /// is used when the inference engine is attempting to perform type inference
  485. /// for a parameter which is typed to a delegate type.
  486. /// </summary>
  487. public interface IInferableInvokable {
  488. /// <summary>
  489. /// Returns the type inferred for parameterType when performing
  490. /// inference for a conversion to delegateType.
  491. /// </summary>
  492. InferenceResult GetInferredType(Type delegateType, Type parameterType);
  493. }
  494. /// <summary>
  495. /// Provides information about the result of a custom object which dynamically
  496. /// infers back types.
  497. ///
  498. /// Currently only used for invokable objects to feedback the types for a delegate
  499. /// type.
  500. /// </summary>
  501. public class InferenceResult {
  502. private readonly Type _type;
  503. private readonly BindingRestrictions _restrictions;
  504. public InferenceResult(Type type, BindingRestrictions restrictions) {
  505. ContractUtils.RequiresNotNull(type, "type");
  506. ContractUtils.RequiresNotNull(restrictions, "restrictions");
  507. _type = type;
  508. _restrictions = restrictions;
  509. }
  510. public Type Type {
  511. get {
  512. return _type;
  513. }
  514. }
  515. public BindingRestrictions Restrictions {
  516. get {
  517. return _restrictions;
  518. }
  519. }
  520. }
  521. }