Exemplo n.º 1
0
        public bool IsValidCallNode(CallNode node, TexlBinding binding, OperationCapabilityMetadata metadata)
        {
            Contracts.AssertValue(node);
            Contracts.AssertValue(binding);
            Contracts.AssertValue(metadata);

            if (!IsValidNode(node, binding))
            {
                SuggestDelegationHint(node, binding);
                return(false);
            }

            // If the node is not row scoped and it's valid then it can be delegated.
            var isRowScoped = binding.IsRowScope(node);

            if (!isRowScoped)
            {
                return(true);
            }

            CallInfo callInfo = binding.GetInfo(node);

            if (callInfo?.Function != null && ((TexlFunction)callInfo.Function).IsRowScopedServerDelegatable(node, binding, metadata))
            {
                return(true);
            }

            var telemetryMessage = string.Format("Kind:{0}, isRowScoped:{1}", node.Kind, isRowScoped);

            SuggestDelegationHintAndAddTelemetryMessage(node, binding, telemetryMessage);
            TrackingProvider.Instance.SetDelegationTrackerStatus(DelegationStatus.UndelegatableFunction, node, binding, _function, DelegationTelemetryInfo.CreateUndelegatableFunctionTelemetryInfo((TexlFunction)callInfo?.Function));
            return(false);
        }
Exemplo n.º 2
0
        private bool IsValidRowScopedDottedNameNode(DottedNameNode node, TexlBinding binding, OperationCapabilityMetadata metadata, out bool isRowScopedDelegationExempted)
        {
            Contracts.AssertValue(node);
            Contracts.AssertValue(binding);

            isRowScopedDelegationExempted = false;
            if (node.Left.Kind == NodeKind.FirstName &&
                binding.IsDelegationExempted(node.Left as FirstNameNode) &&
                binding.IsLambdaScoped(node.Left as FirstNameNode))
            {
                isRowScopedDelegationExempted = true;

                return(true);
            }

            if (node.Left.Kind == NodeKind.DottedName)
            {
                return(IsValidRowScopedDottedNameNode(node.Left.AsDottedName(), binding, metadata, out isRowScopedDelegationExempted));
            }

            if (node.Left.Kind == NodeKind.Call && binding.GetInfo(node.Left as CallNode).Function is AsTypeFunction)
            {
                return(IsValidCallNode(node.Left as CallNode, binding, metadata));
            }

            // We only allow dotted or firstname node on LHS for now, with exception of AsType.
            return(node.Left.Kind == NodeKind.FirstName);
        }
        private bool TryGetDsNode(FirstNameNode firstName, TexlBinding binding, out FirstNameNode dsNode)
        {
            Contracts.AssertValueOrNull(firstName);
            Contracts.AssertValue(binding);

            dsNode = null;
            if (firstName == null || !binding.GetType(firstName).IsTable)
            {
                return(false);
            }

            var firstNameInfo = binding.GetInfo(firstName);

            if (firstNameInfo == null || firstNameInfo.Kind != BindKind.Data)
            {
                return(false);
            }

            if (binding.EntityScope == null || !binding.EntityScope.TryGetEntity(firstNameInfo.Name, out IExternalDataSource _))
            {
                return(false);
            }

            dsNode = firstName;
            return(true);
        }
Exemplo n.º 4
0
        private bool TryGetEntityInfo(CallNode callNode, TexlBinding binding, out IExpandInfo entityInfo)
        {
            Contracts.AssertValueOrNull(callNode);
            Contracts.AssertValue(binding);

            entityInfo = null;
            if (callNode == null || !binding.GetType(callNode).IsTable)
            {
                return(false);
            }

            var callInfo = binding.GetInfo(callNode);

            if (callInfo == null)
            {
                return(false);
            }

            var function = callInfo.Function;

            if (function == null)
            {
                return(false);
            }

            return(function.TryGetEntityInfo(callNode, binding, out entityInfo));
        }
Exemplo n.º 5
0
        public static bool FindCurFuncAndArgs(TexlNode curNode, int cursorPos, TexlBinding binding, out TexlFunction curFunc, out int argIndex, out int argCount, out DType expectedType)
        {
            Contracts.AssertValue(curNode);
            Contracts.AssertValue(binding);

            if (curNode.Kind == NodeKind.Call)
            {
                CallNode callNode = curNode.CastCall();
                if (callNode.Token.Span.Lim <= cursorPos && callNode.ParenClose != null && cursorPos <= callNode.ParenClose.Span.Min)
                {
                    CallInfo info = binding.GetInfo(callNode);

                    if (info.Function != null)
                    {
                        curFunc      = info.Function;
                        argIndex     = 0;
                        argCount     = callNode.Args.Count;
                        expectedType = curFunc.ParamTypes.Length > 0 ? curFunc.ParamTypes[0] : DType.Error;

                        return(true);
                    }
                }
            }

            if (IntellisenseHelper.TryGetInnerMostFunction(curNode, binding, out curFunc, out argIndex, out argCount))
            {
                expectedType = curFunc.ParamTypes.Length > argIndex ? curFunc.ParamTypes[argIndex] : DType.Error;
                return(true);
            }

            expectedType = DType.Error;
            return(false);
        }
        private bool TryGetDsNodes(CallNode callNode, TexlBinding binding, out IList <FirstNameNode> dsInfos)
        {
            Contracts.AssertValueOrNull(callNode);
            Contracts.AssertValue(binding);

            dsInfos = new List <FirstNameNode>();
            if (callNode == null || !(binding.GetType(callNode).IsAggregate))
            {
                return(false);
            }

            var callInfo = binding.GetInfo(callNode);

            if (callInfo == null)
            {
                return(false);
            }

            var function = callInfo.Function;

            if (function == null)
            {
                return(false);
            }

            return(function.TryGetDataSourceNodes(callNode, binding, out dsInfos));
        }
        private bool TryGetDsInfo(CallNode callNode, TexlBinding binding, out IExternalDataSource dsInfo)
        {
            Contracts.AssertValueOrNull(callNode);
            Contracts.AssertValue(binding);

            dsInfo = null;
            if (callNode == null || !binding.IsDelegatable(callNode) || !binding.GetType(callNode).IsTable)
            {
                return(false);
            }

            var callInfo = binding.GetInfo(callNode);

            if (callInfo == null)
            {
                return(false);
            }

            var function = callInfo.Function;

            if (function == null)
            {
                return(false);
            }

            bool success = function.TryGetDataSource(callNode, binding, out var external);

            dsInfo = (IExternalDataSource)external;
            return(success);
        }
Exemplo n.º 8
0
        // Gets the inner most function and the current arg index from the current node, if any.
        // If there is no inner most function, current arg index will be -1
        // and argument count will be -1.
        internal static bool TryGetInnerMostFunction(TexlNode nodeCur, TexlBinding bind, out TexlFunction funcCur, out int iarg, out int carg)
        {
            Contracts.AssertValue(nodeCur);
            Contracts.AssertValue(bind);

            TexlNode nodeParent  = nodeCur.Parent;
            TexlNode nodeCallArg = nodeCur;
            CallNode callNode    = null;

            while (nodeParent != null)
            {
                if (nodeParent.Kind == NodeKind.Call)
                {
                    callNode = nodeParent.AsCall();
                    break;
                }
                // The last node before a call's list node is the call arg.
                if (nodeParent.Kind != NodeKind.List)
                {
                    nodeCallArg = nodeParent;
                }

                nodeParent = nodeParent.Parent;
            }

            if (callNode == null)
            {
                iarg    = -1;
                carg    = -1;
                funcCur = null;
                return(false);
            }

            Contracts.AssertValue(nodeCallArg);

            CallInfo info = bind.GetInfo(callNode);

            if (info.Function != null)
            {
                carg = callNode.Args.Count;
                for (iarg = 0; iarg < carg; iarg++)
                {
                    if (callNode.Args.Children[iarg] == nodeCallArg)
                    {
                        break;
                    }
                }
                Contracts.Assert(iarg < carg);
                funcCur = (TexlFunction)info.Function;
                return(true);
            }

            iarg    = -1;
            carg    = -1;
            funcCur = null;
            return(false);
        }
Exemplo n.º 9
0
        private bool IsUserCallNodeDelegable(TexlNode node, TexlBinding binding)
        {
            if ((node is DottedNameNode) &&
                (node.AsDottedName().Left is CallNode) &&
                (binding.GetInfo(node.AsDottedName().Left.AsCall()).Function is ICustomDelegationFunction customDelFunc) &&
                customDelFunc.IsUserCallNodeDelegable())
            {
                return(true);
            }

            return(false);
        }
Exemplo n.º 10
0
        // Verifies if provided column node supports delegation.
        protected bool IsDelegatableColumnNode(FirstNameNode node, TexlBinding binding, IOpDelegationStrategy opDelStrategy, DelegationCapability capability)
        {
            Contracts.AssertValue(node);
            Contracts.AssertValue(binding);
            Contracts.AssertValueOrNull(opDelStrategy);
            Contracts.Assert(binding.IsRowScope(node));

            FirstNameInfo firstNameInfo = binding.GetInfo(node.AsFirstName());

            if (firstNameInfo == null)
            {
                return(false);
            }

            IDelegationMetadata metadata = GetCapabilityMetadata(firstNameInfo);

            // This means that this row scoped field is from some parent scope which is non-delegatable. That should deny delegation at that point.
            // For this scope, this means that value will be provided from some other source.
            // For example, AddColumns(CDS, "Column1", LookUp(CDS1, Name in FirstName))
            // CDS - *[Name:s], CDS1 - *[FirstName:s]
            if (metadata == null)
            {
                return(true);
            }

            var columnName = firstNameInfo.Name;

            Contracts.AssertValid(columnName);

            var columnPath = DPath.Root.Append(columnName);

            if (!metadata.FilterDelegationMetadata.IsDelegationSupportedByColumn(columnPath, capability))
            {
                var safeColumnName = CharacterUtils.MakeSafeForFormatString(columnName.Value);
                var message        = string.Format(StringResources.Get(TexlStrings.OpNotSupportedByColumnSuggestionMessage_OpNotSupportedByColumn), safeColumnName);
                SuggestDelegationHintAndAddTelemetryMessage(node, binding, message, TexlStrings.OpNotSupportedByColumnSuggestionMessage_OpNotSupportedByColumn, safeColumnName);
                TrackingProvider.Instance.SetDelegationTrackerStatus(DelegationStatus.NoDelSupportByColumn, node, binding, _function, DelegationTelemetryInfo.CreateNoDelSupportByColumnTelemetryInfo(firstNameInfo));
                return(false);
            }

            // If there is any operator applied on this node then check if column supports operation.
            if (opDelStrategy != null && !opDelStrategy.IsOpSupportedByColumn(metadata.FilterDelegationMetadata, node.AsFirstName(), columnPath, binding))
            {
                return(false);
            }

            return(true);
        }
Exemplo n.º 11
0
        public static DelegationTelemetryInfo CreateImpureNodeTelemetryInfo(TexlNode node, TexlBinding binding = null)
        {
            Contracts.AssertValue(node);
            Contracts.AssertValueOrNull(binding);

            switch (node.Kind)
            {
            case NodeKind.Call:
                var callNode = node.AsCall();
                var funcName = binding?.GetInfo(callNode)?.Function?.Name ?? string.Empty;
                return(new DelegationTelemetryInfo(funcName));

            default:
                return(new DelegationTelemetryInfo(node.ToString()));
            }
        }
Exemplo n.º 12
0
        protected bool IsValidNode(TexlNode node, TexlBinding binding)
        {
            Contracts.AssertValue(node);
            Contracts.AssertValue(binding);

            bool isAsync = binding.IsAsync(node);
            bool isPure  = binding.IsPure(node);


            if (node is DottedNameNode &&
                ((binding.GetType(node.AsDottedName().Left).Kind == DKind.OptionSet && binding.GetType(node).Kind == DKind.OptionSetValue) ||
                 (binding.GetType(node.AsDottedName().Left).Kind == DKind.View && binding.GetType(node).Kind == DKind.ViewValue)))
            {
                // OptionSet and View Access are delegable despite being async
                return(true);
            }

            if (node is CallNode && (binding.IsBlockScopedConstant(node) ||
                                     (binding.GetInfo(node as CallNode).Function is AsTypeFunction)))
            {
                // AsType is delegable despite being async
                return(true);
            }

            // Async predicates and impure nodes are not supported.
            // Let CallNodes for User() be marked as being Valid to allow
            // expressions with User() calls to be delegated
            if (!(IsUserCallNodeDelegable(node, binding)) && (isAsync || !isPure))
            {
                var telemetryMessage = string.Format("Kind:{0}, isAsync:{1}, isPure:{2}", node.Kind, isAsync, isPure);
                SuggestDelegationHintAndAddTelemetryMessage(node, binding, telemetryMessage);

                if (isAsync)
                {
                    TrackingProvider.Instance.SetDelegationTrackerStatus(DelegationStatus.AsyncPredicate, node, binding, _function);
                }

                if (!isPure)
                {
                    TrackingProvider.Instance.SetDelegationTrackerStatus(DelegationStatus.ImpureNode, node, binding, _function, DelegationTelemetryInfo.CreateImpureNodeTelemetryInfo(node, binding));
                }

                return(false);
            }

            return(true);
        }
Exemplo n.º 13
0
        public override void Visit(FirstNameNode node)
        {
            FirstNameInfo info = _binding.GetInfo(node);

            var name = node.Ident.Name.Value;

            // Only include dependencies from the incoming context (Fields)
            // defined at the top level (NestDst==1)
            if ((info.NestDst == 1 && info.Kind == BindKind.LambdaField) ||
                (info.Kind == BindKind.ScopeVariable) ||
                (info.Kind == BindKind.PowerFxResolvedObject))
            {
                _vars.Add(name);
            }

            base.Visit(node);
        }
Exemplo n.º 14
0
        internal static bool TryConvertNodeToDPath(TexlBinding binding, DottedNameNode node, out DPath path)
        {
            Contracts.AssertValue(binding);
            Contracts.AssertValue(node);

            if (node.Left is DottedNameNode && TryConvertNodeToDPath(binding, node.Left as DottedNameNode, out path))
            {
                DName  rightNodeName = node.Right.Name;
                string possibleRename;
                if (binding.TryGetReplacedIdentName(node.Right, out possibleRename))
                {
                    rightNodeName = new DName(possibleRename);
                }

                path = path.Append(rightNodeName);
                return(true);
            }
            else if (node.Left is FirstNameNode firstName)
            {
                if (binding.GetInfo(firstName).Kind == BindKind.LambdaFullRecord)
                {
                    DName rightNodeName = node.Right.Name;
                    if (binding.TryGetReplacedIdentName(node.Right, out string rename))
                    {
                        rightNodeName = new DName(rename);
                    }

                    path = DPath.Root.Append(rightNodeName);
                    return(true);
                }

                // Check if the access was renamed:
                DName  leftNodeName = firstName.Ident.Name;
                string possibleRename;
                if (binding.TryGetReplacedIdentName(firstName.Ident, out possibleRename))
                {
                    leftNodeName = new DName(possibleRename);
                }

                path = DPath.Root.Append(leftNodeName).Append(node.Right.Name);
                return(true);
            }

            path = DPath.Root;
            return(false);
        }
Exemplo n.º 15
0
        public override LazyList <string> Visit(FirstNameNode node, Precedence parentPrecedence)
        {
            Contracts.AssertValue(node);

            var info = _binding?.GetInfo(node);

            if (info != null && info.Kind != BindKind.Unknown)
            {
                return(LazyList <string> .Of($"#${Enum.GetName(typeof(BindKind), info.Kind)}$#"));
            }
            if (node.Ident.AtToken == null)
            {
                return(LazyList <string> .Of("#$firstname$#"));
            }
            else
            {
                return(LazyList <string> .Of("#$disambiguation$#"));
            }
        }
            private static bool TryGetLocalScopeInfo(TexlNode node, TexlBinding binding, out ScopedNameLookupInfo info)
            {
                Contracts.AssertValue(node);
                Contracts.AssertValue(binding);

                if (node.Kind == NodeKind.FirstName)
                {
                    FirstNameNode curNode       = node.CastFirstName();
                    var           firstNameInfo = binding.GetInfo(curNode);
                    if (firstNameInfo.Kind == BindKind.ScopeArgument)
                    {
                        info = (ScopedNameLookupInfo)firstNameInfo.Data;
                        return(true);
                    }
                }

                info = new ScopedNameLookupInfo();
                return(false);
            }
Exemplo n.º 17
0
        private bool VerifyFirstNameNodeIsValidSortOrderEnum(FirstNameNode node, TexlBinding binding)
        {
            Contracts.AssertValue(node);
            Contracts.AssertValue(binding);

            var firstNameInfo = binding.GetInfo(node);

            if (firstNameInfo == null || firstNameInfo.Kind != BindKind.Enum)
            {
                return(false);
            }


            if (!binding.NameResolver.TryLookupEnum(new DName(LanguageConstants.SortOrderEnumStringInvariant), out var lookupInfo))
            {
                return(false);
            }

            DType type = binding.GetType(node);

            return(type == lookupInfo.Type);
        }
Exemplo n.º 18
0
        private bool TryGetValidSortOrderNode(FirstNameNode node, TexlBinding binding, out string sortOrder)
        {
            Contracts.AssertValue(node);
            Contracts.AssertValue(binding);

            sortOrder = "";
            var info = binding.GetInfo(node).VerifyValue();

            if (info.Kind != BindKind.Enum)
            {
                return(false);
            }

            string order = info.Data as string;

            if (order == null)
            {
                return(false);
            }

            return(IsValidOrderString(order, out sortOrder));
        }
            private static bool TryGetNamespaceFunctions(TexlNode node, TexlBinding binding, out IEnumerable <TexlFunction> functions)
            {
                Contracts.AssertValue(node);
                Contracts.AssertValue(binding);

                FirstNameNode curNode = node.AsFirstName();

                if (curNode == null)
                {
                    functions = EmptyEnumerator <TexlFunction> .Instance;
                    return(false);
                }

                FirstNameInfo firstNameInfo = binding.GetInfo(curNode).VerifyValue();

                Contracts.AssertValid(firstNameInfo.Name);

                DPath namespacePath = new DPath().Append(firstNameInfo.Name);

                functions = binding.NameResolver.LookupFunctionsInNamespace(namespacePath);

                return(functions.Any());
            }
            private static bool TryGetEnumInfo(IntellisenseData.IntellisenseData data, TexlNode node, TexlBinding binding, out EnumSymbol enumSymbol)
            {
                Contracts.AssertValue(node);
                Contracts.AssertValue(binding);

                FirstNameNode curNode = node.AsFirstName();

                if (curNode == null)
                {
                    enumSymbol = null;
                    return(false);
                }

                FirstNameInfo firstNameInfo = binding.GetInfo(curNode).VerifyValue();

                if (firstNameInfo.Kind != BindKind.Enum)
                {
                    enumSymbol = null;
                    return(false);
                }

                return(data.TryGetEnumSymbol(firstNameInfo.Name, binding, out enumSymbol));
            }
Exemplo n.º 21
0
        public bool CheckForFullyQualifiedFieldAccess(bool isRHSDelegableTable, BinaryOpNode binaryOpNode, TexlBinding binding, TexlNode node, ref DName columnName, ref FirstNameInfo info)
        {
            Contracts.AssertValue(binaryOpNode);
            Contracts.AssertValue(binding);
            Contracts.AssertValue(node);

            // Check for fully qualified field access
            var firstNameNode  = isRHSDelegableTable ? binaryOpNode.Left?.AsFirstName() : binaryOpNode.Right?.AsFirstName();
            var dottedNameNode = isRHSDelegableTable ? binaryOpNode.Left?.AsDottedName() : binaryOpNode.Right?.AsDottedName();

            if (dottedNameNode != null && dottedNameNode.Left is FirstNameNode possibleScopeAccess && (info = binding.GetInfo(possibleScopeAccess))?.Kind == BindKind.LambdaFullRecord)
            {
                columnName = dottedNameNode.Right.Name;
            }
Exemplo n.º 22
0
        public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding)
        {
            Contracts.AssertValue(callNode);
            Contracts.AssertValue(binding);

            if (!CheckArgsCount(callNode, binding))
            {
                return(false);
            }

            SortOpMetadata      metadata           = null;
            IDelegationMetadata delegationMetadata = null;
            IExternalDataSource dataSource;

            if (TryGetEntityMetadata(callNode, binding, out delegationMetadata))
            {
                if (!binding.Document.Properties.EnabledFeatures.IsEnhancedDelegationEnabled ||
                    !TryGetValidDataSourceForDelegation(callNode, binding, DelegationCapability.ArrayLookup, out _))
                {
                    SuggestDelegationHint(callNode, binding);
                    return(false);
                }

                metadata = delegationMetadata.SortDelegationMetadata.VerifyValue();
            }
            else
            {
                if (!TryGetValidDataSourceForDelegation(callNode, binding, DelegationCapability.Sort, out dataSource))
                {
                    return(false);
                }

                metadata = dataSource.DelegationMetadata.SortDelegationMetadata;
            }

            TexlNode[] args = callNode.Args.Children.VerifyValue();
            TexlNode   arg1 = args[1].VerifyValue();

            // For now, we are only supporting delegation for Sort operations where second argument is column name.
            // For example, Sort(CDS, Value)
            FirstNameNode firstName = arg1.AsFirstName();

            if (firstName == null)
            {
                SuggestDelegationHint(arg1, binding);
                AddSuggestionMessageToTelemetry("Arg1 is not a FirstName node.", arg1, binding);
                DelegationTrackerCore.SetDelegationTrackerStatus(DelegationStatus.UnSupportedSortArg, arg1, binding, this, DelegationTelemetryInfo.CreateEmptyDelegationTelemetryInfo());
                return(false);
            }

            FirstNameInfo firstNameInfo = binding.GetInfo(firstName);

            if (firstNameInfo == null)
            {
                return(false);
            }

            DPath columnName = DPath.Root.Append(firstNameInfo.Name);

            if (!metadata.IsDelegationSupportedByColumn(columnName, DelegationCapability.Sort))
            {
                SuggestDelegationHint(firstName, binding);
                DelegationTrackerCore.SetDelegationTrackerStatus(DelegationStatus.NoDelSupportByColumn, firstName, binding, this, DelegationTelemetryInfo.CreateNoDelSupportByColumnTelemetryInfo(firstNameInfo));
                return(false);
            }

            const string defaultSortOrder = LanguageConstants.AscendingSortOrderString;
            int          cargs            = args.Count();

            // Verify that the third argument (If present) is an Enum or string literal.
            if (cargs < 3 && IsSortOrderSuppportedByColumn(callNode, binding, defaultSortOrder, metadata, columnName))
            {
                return(true);
            }

            // TASK: 6237100 - Binder: Propagate errors in subtree of the callnode to the call node itself
            // Only FirstName, DottedName and StrLit non-async nodes are supported for arg2.
            TexlNode arg2 = args[2].VerifyValue();

            if (!IsValidSortOrderNode(arg2, metadata, binding, columnName))
            {
                SuggestDelegationHint(arg2, binding);
                return(false);
            }

            return(true);
        }
Exemplo n.º 23
0
            public override void PostVisit(DottedNameNode node)
            {
                Contracts.AssertValue(node);

                DType                  lhsType = _txb.GetType(node.Left);
                DType                  typeRhs = DType.Invalid;
                DName                  nameRhs = node.Right.Name;
                FirstNameInfo          firstNameInfo;
                FirstNameNode          firstNameNode;
                IExternalTableMetadata tableMetadata;
                DType                  nodeType = DType.Unknown;

                if (node.Left.Kind != NodeKind.FirstName &&
                    node.Left.Kind != NodeKind.DottedName)
                {
                    SetDottedNameError(node, TexlStrings.ErrInvalidName);
                    return;
                }

                nameRhs = GetLogicalNodeNameAndUpdateDisplayNames(lhsType, node.Right);

                if (!lhsType.TryGetType(nameRhs, out typeRhs))
                {
                    SetDottedNameError(node, TexlStrings.ErrInvalidName);
                    return;
                }

                // There are two cases:
                // 1. RHS could be an option set.
                // 2. RHS could be a data entity.
                // 3. RHS could be a column name and LHS would be a datasource.
                if (typeRhs.IsOptionSet)
                {
                    nodeType = typeRhs;
                }
                else if (typeRhs.IsExpandEntity)
                {
                    var entityInfo = typeRhs.ExpandInfo;
                    Contracts.AssertValue(entityInfo);

                    string entityPath = string.Empty;
                    if (lhsType.HasExpandInfo)
                    {
                        entityPath = lhsType.ExpandInfo.ExpandPath.ToString();
                    }

                    DType expandedEntityType = GetExpandedEntityType(typeRhs, entityPath);

                    var parentDataSource = entityInfo.ParentDataSource;
                    var metadata         = new DataTableMetadata(parentDataSource.Name, parentDataSource.Name);
                    nodeType = DType.CreateMetadataType(new DataColumnMetadata(typeRhs.ExpandInfo.Name, expandedEntityType, metadata));
                }
                else if ((firstNameNode = node.Left.AsFirstName()) != null && (firstNameInfo = _txb.GetInfo(firstNameNode)) != null)
                {
                    var tabularDataSourceInfo = firstNameInfo.Data as IExternalTabularDataSource;
                    tableMetadata = tabularDataSourceInfo?.TableMetadata;
                    if (tableMetadata == null || !tableMetadata.TryGetColumn(nameRhs.Value, out var columnMetadata) || !IsColumnMultiChoice(columnMetadata))
                    {
                        SetDottedNameError(node, TexlStrings.ErrInvalidName);
                        return;
                    }

                    var metadata = new DataTableMetadata(tabularDataSourceInfo.Name, tableMetadata.DisplayName);
                    nodeType = DType.CreateMetadataType(new DataColumnMetadata(columnMetadata, metadata));
                }
                else
                {
                    SetDottedNameError(node, TexlStrings.ErrInvalidName);
                    return;
                }

                Contracts.AssertValid(nodeType);

                _txb.SetType(node, nodeType);
                _txb.SetInfo(node, new DottedNameInfo(node));
            }