Пример #1
0
        internal override Expression ResolveEntityNames(
            ParserContext parser)
        {
            FunctionDefinition funcDef; // used in multiple places.
            FieldDefinition    fieldDec;

            this.Root = this.Root.ResolveEntityNames(parser);
            Expression root  = this.Root;
            string     field = this.FieldToken.Value;

            if (root is NamespaceReference)
            {
                // already a fully qualified namespace, therefore imports don't matter.
                string         fullyQualifiedName = ((NamespaceReference)root).Template.Name + "." + field;
                TopLevelEntity entity             = this.Owner.FileScope.FileScopeEntityLookup.DoEntityLookup(fullyQualifiedName, parser.CurrentCodeContainer);
                if (entity != null)
                {
                    return(ResolverPipeline.ConvertStaticReferenceToExpression(entity, this.FirstToken, this.Owner));
                }

                NamespaceReferenceTemplate nrt = this.Owner.FileScope.FileScopeEntityLookup.DoNamespaceLookup(fullyQualifiedName, parser.CurrentCodeContainer);
                if (nrt != null)
                {
                    return(new NamespaceReference(this.FirstToken, this.Owner, nrt));
                }

                throw new ParserException(this, "Could not find class or function by the name of: '" + fullyQualifiedName + "'");
            }

            if (root is ClassReference)
            {
                ClassDefinition cd = ((ClassReference)root).ClassDefinition;

                funcDef = cd.GetMethod(field, false);
                if (funcDef != null)
                {
                    if (!funcDef.Modifiers.HasStatic)
                    {
                        string className    = cd.NameToken.Value;
                        string functionName = funcDef.NameToken.Value;

                        throw new ParserException(this.DotToken, "'" + className + "." + functionName + "' is not a static method, but it is being used as though it is static.");
                    }

                    Node.EnsureAccessIsAllowed(this.FieldToken, this.Owner, funcDef);

                    return(new FunctionReference(this.FirstToken, funcDef, this.Owner));
                }

                fieldDec = cd.GetField(field, false);
                if (fieldDec != null)
                {
                    if (!fieldDec.Modifiers.HasStatic)
                    {
                        throw new ParserException(this.DotToken, "Cannot make a static reference to a non-static field.");
                    }

                    Node.EnsureAccessIsAllowed(this.FieldToken, this.Owner, fieldDec);

                    return(new FieldReference(this.FirstToken, fieldDec, this.Owner));
                }

                // TODO: typeof(class name) is less error prone with localization conflicts.
                // However there's a Core.typeOf() method that sort of conflicts.
                // TODO: if this notation is kept, then this needs to be split into two class keywords
                // since they do different things.
                if (field == parser.Keywords.CLASS)
                {
                    return(new ClassReferenceLiteral(this.FirstToken, cd, this.Owner));
                }

                // TODO: nested classes, enums, constants

                // TODO: show spelling suggestions.
                throw new ParserException(this.FieldToken, "No static fields or methods named '" + field + "' on the class " + cd.NameToken.Value + ".");
            }

            if (root is BaseKeyword)
            {
                ClassDefinition thisClass = null;
                if (this.Owner != null)
                {
                    if (this.Owner is FunctionDefinition)
                    {
                        thisClass = this.Owner.Owner as ClassDefinition;
                    }
                    else
                    {
                        thisClass = this.Owner as ClassDefinition;
                    }
                }

                if (thisClass == null)
                {
                    throw new ParserException(root, "'base' keyword can only be used inside classes.");
                }

                ClassDefinition cd = thisClass.BaseClass;
                if (cd == null)
                {
                    throw new ParserException(root, "'base' keyword can only be used inside classes that extend from another class.");
                }

                FunctionDefinition fd = cd.GetMethod(field, true);
                if (fd == null)
                {
                    throw new ParserException(this.DotToken, "Cannot find a method by that name in the base class chain.");
                }

                if (fd.Modifiers.HasStatic)
                {
                    throw new ParserException(this.DotToken, "Cannot reference static methods using 'base' keyword.");
                }

                Node.EnsureAccessIsAllowed(this.DotToken, this.Owner, fd);

                return(new BaseMethodReference(this.FirstToken, this.DotToken, this.FieldToken, this.Owner));
            }

            if (root is ThisKeyword)
            {
                ClassDefinition cd    = null;
                TopLevelEntity  owner = this.TopLevelEntity;
                if (owner is FunctionDefinition)
                {
                    FunctionDefinition functionOwner = (FunctionDefinition)owner;
                    if (functionOwner.Modifiers.HasStatic)
                    {
                        throw new ParserException(this.Root, "'this' keyword cannot be used in static methods.");
                    }

                    if (!(functionOwner.Owner is ClassDefinition))
                    {
                        throw new ParserException(this.Root, "'this' keyword cannot be used in a function that isn't in a class.");
                    }

                    cd = (ClassDefinition)owner.Owner;
                }
                else if (owner is FieldDefinition)
                {
                    if (((FieldDefinition)owner).Modifiers.HasStatic)
                    {
                        throw new ParserException(this.Root, "'this' keyword cannot be used in static fields.");
                    }
                    cd = (ClassDefinition)owner.Owner;
                }
                else if (owner is ConstructorDefinition)
                {
                    if (((ConstructorDefinition)owner).Modifiers.HasStatic)
                    {
                        throw new ParserException(this.Root, "'this', keyword cannot be used in static constructors.");
                    }
                    cd = (ClassDefinition)owner.Owner;
                }
                else
                {
                    throw new ParserException(this.Root, "'this' keyword must be used inside a class.");
                }

                funcDef = cd.GetMethod(field, true);
                if (funcDef != null)
                {
                    if (funcDef.Modifiers.HasStatic)
                    {
                        throw new ParserException(this.DotToken, "This method is static and must be referenced by the class name, not 'this'.");
                    }
                    Node.EnsureAccessIsAllowed(this.DotToken, this.Owner, funcDef);
                    return(new FunctionReference(this.FirstToken, funcDef, this.Owner));
                }

                FieldDefinition fieldDef = cd.GetField(field, true);
                if (fieldDef != null)
                {
                    if (fieldDef.Modifiers.HasStatic)
                    {
                        throw new ParserException(this.DotToken, "This field is static and must be referenced by the class name, not 'this'.");
                    }
                    Node.EnsureAccessIsAllowed(this.DotToken, this.Owner, fieldDef);

                    return(new FieldReference(this.FirstToken, fieldDef, this.Owner));
                }

                // TODO: show suggestions in the error message for anything close to what was typed.
                throw new ParserException(this.FieldToken, "The class '" + cd.NameToken.Value + "' does not have a field named '" + field + "'.");
            }

            if (this.Root is EnumReference)
            {
                EnumDefinition enumDef = ((EnumReference)this.Root).EnumDefinition;

                if (field == parser.Keywords.FIELD_ENUM_LENGTH)
                {
                    return(new IntegerConstant(this.FirstToken, enumDef.Items.Length, this.Owner));
                }
                if (field == parser.Keywords.FIELD_ENUM_MAX)
                {
                    return(new SpecialEntity.EnumMaxFunction(this.FirstToken, enumDef, this.Owner));
                }
                if (field == parser.Keywords.FIELD_ENUM_VALUES)
                {
                    return(new SpecialEntity.EnumValuesFunction(this.FirstToken, enumDef, this.Owner));
                }

                return(new EnumFieldReference(this.FirstToken, enumDef, this.FieldToken, this.Owner));
            }

            // This is done here in the resolver instead of the parser because some unallowed
            // field names (such as .class) are valid.
            if (field == parser.Keywords.CLASS)
            {
                if (this.Root is Variable)
                {
                    throw new ParserException(this.Root, "'" + ((Variable)this.Root).Name + "' is not a class.");
                }
                throw new ParserException(this.DotToken, ".class can only be applied to class names.");
            }
            parser.VerifyIdentifier(this.FieldToken);

            return(this);
        }
Пример #2
0
        private void EnsureModifiersAndTypeSignatureConsistencyForClassMethods(TypeContext tc)
        {
            ClassDefinition classDef     = (ClassDefinition)this.Owner;
            ClassDefinition baseClass    = classDef.BaseClass;
            bool            hasBaseClass = baseClass != null;

            if (hasBaseClass)
            {
                FieldDefinition hiddenField = baseClass.GetField(this.NameToken.Value, true);
                if (hiddenField != null)
                {
                    throw new ParserException(this, "This function definition hides a field from a parent class.");
                }
            }

            if (!hasBaseClass)
            {
                if (this.Modifiers.HasOverride)
                {
                    throw new ParserException(this, "Cannot mark a method as 'override' if it has no base class.");
                }
            }
            else
            {
                FunctionDefinition overriddenFunction = baseClass.GetMethod(this.NameToken.Value, true);
                if (overriddenFunction != null)
                {
                    if (!this.Modifiers.HasOverride)
                    {
                        if (this.CompilationScope.IsCrayon)
                        {
                            // TODO: just warn if not present
                        }
                        else
                        {
                            throw new ParserException(this, "This function hides another function from a parent class. If overriding is intentional, use the 'override' keyword.");
                        }
                    }

                    if (overriddenFunction.Modifiers.HasStatic)
                    {
                        throw new ParserException(this, "Cannot override a static method.");
                    }

                    if (overriddenFunction.Modifiers.AccessModifierType != this.Modifiers.AccessModifierType)
                    {
                        throw new ParserException(this, "This method defines a different access modifier than its overridden parent.");
                    }

                    if (overriddenFunction.Modifiers.AccessModifierType == AccessModifierType.PRIVATE)
                    {
                        throw new ParserException(this, "Cannot override a private method.");
                    }

                    if (overriddenFunction.Modifiers.HasInternal &&
                        overriddenFunction.CompilationScope != this.CompilationScope)
                    {
                        throw new ParserException(this, "Cannot override this method. It is marked as internal and is located in a different assembly.");
                    }

                    if (overriddenFunction.ResolvedReturnType != this.ResolvedReturnType)
                    {
                        if (overriddenFunction.ResolvedReturnType == tc.ANY &&
                            this.ResolvedReturnType == tc.OBJECT)
                        {
                            // This is fine.
                        }
                        else
                        {
                            throw new ParserException(this, "This function returns a different type than its overridden parent.");
                        }
                    }

                    if (overriddenFunction.ArgTypes.Length != this.ArgTypes.Length)
                    {
                        throw new ParserException(this, "This function has a different number of arguments than its overridden parent.");
                    }

                    if (overriddenFunction.MinArgCount != this.MinArgCount)
                    {
                        throw new ParserException(this, "This function has a different number of optional arguments than its overridden parent.");
                    }

                    // TODO: if you are overridding a function between a statically and dynamically typed languagte,
                    // the dynamic language should pick up the types from the statically typed language or the
                    // statically typed language should require "object" types in its signature for the other direction.
                    for (int i = 0; i < this.ArgTypes.Length; ++i)
                    {
                        ResolvedType expected = overriddenFunction.ResolvedArgTypes[i];
                        ResolvedType actual   = this.ResolvedArgTypes[i];
                        if (actual != expected &&
                            !(actual == tc.OBJECT && expected == tc.ANY))
                        {
                            throw new ParserException(this, "This function has arguments that are different types than its overridden parent.");
                        }
                    }
                }
                else
                {
                    if (this.Modifiers.HasOverride)
                    {
                        throw new ParserException(this, "This function is marked as 'override', but there is no method in a parent class with the same name.");
                    }
                }
            }
        }
Пример #3
0
        internal override Expression ResolveTypes(ParserContext parser, TypeResolver typeResolver)
        {
            this.Root.ResolveTypes(parser, typeResolver);

            string field = this.FieldToken.Value;

            if (this.Root is EnumReference)
            {
                throw new System.NotImplementedException();
            }

            ResolvedType rootType = this.Root.ResolvedType;
            TypeContext  tc       = parser.TypeContext;

            // TODO: all of this needs to be localized.
            switch (rootType.Category)
            {
            case ResolvedTypeCategory.NULL:
                throw new ParserException(this.DotToken, "Cannot dereference a field from null.");

            case ResolvedTypeCategory.ANY:
                // ¯\_(ツ)_/¯
                this.ResolvedType = parser.TypeContext.ANY;
                return(this);

            case ResolvedTypeCategory.INTEGER:
                throw new ParserException(this.DotToken, "Integers do not have any fields.");

            case ResolvedTypeCategory.FLOAT:
                throw new ParserException(this.DotToken, "Floating point decimals do not have any fields.");

            case ResolvedTypeCategory.BOOLEAN:
                throw new ParserException(this.DotToken, "Booleans do not have any fields.");

            case ResolvedTypeCategory.STRING:
                switch (field)
                {
                case "length":
                    this.ResolvedType = tc.INTEGER;
                    if (this.Root is StringConstant)
                    {
                        return(new IntegerConstant(this.FirstToken, ((StringConstant)this.Root).Value.Length, this.Owner));
                    }
                    return(this);

                case "contains": return(BuildPrimitiveMethod(tc.BOOLEAN, tc.STRING));

                case "endsWith": return(BuildPrimitiveMethod(tc.BOOLEAN, tc.STRING));

                case "indexOf": return(BuildPrimitiveMethod(tc.INTEGER, tc.STRING));

                case "lower": return(BuildPrimitiveMethod(tc.STRING));

                case "ltrim": return(BuildPrimitiveMethod(tc.STRING));

                case "replace": return(BuildPrimitiveMethod(tc.STRING, tc.STRING, tc.STRING));

                case "reverse": return(BuildPrimitiveMethod(tc.STRING));

                case "rtrim": return(BuildPrimitiveMethod(tc.STRING));

                case "split": return(BuildPrimitiveMethod(ResolvedType.ListOrArrayOf(tc.STRING), tc.STRING));

                case "startsWith": return(BuildPrimitiveMethod(tc.BOOLEAN, tc.STRING));

                case "trim": return(BuildPrimitiveMethod(tc.STRING));

                case "upper": return(BuildPrimitiveMethod(tc.STRING));

                // common mistakes
                case "join":
                    throw new ParserException(this.DotToken, "Strings do not have a .join(list) method. Did you mean to do list.join(string)?");

                case "size":
                    throw new ParserException(this.DotToken, "Strings do not have a .size() method. Did you mean to use .length?");

                default:
                    throw new ParserException(this.DotToken, "Strings do not have that method.");
                }

            case ResolvedTypeCategory.LIST:
                ResolvedType itemType = rootType.ListItemType;
                switch (field)
                {
                case "length":
                    this.ResolvedType = tc.INTEGER;
                    return(this);

                case "filter":
                case "map":
                    if (this.CompilationScope.IsStaticallyTyped)
                    {
                        // TODO: for Acrylic, this needs to have a way to indicate that resolution should be attempted
                        // again once the function return type is known.
                        throw new System.NotImplementedException();
                    }
                    this.ResolvedType = tc.ANY;
                    return(this);

                case "add": return(BuildPrimitiveMethod(tc.VOID, itemType));

                case "choice": return(BuildPrimitiveMethod(tc.VOID));

                case "clear": return(BuildPrimitiveMethod(tc.VOID));

                case "clone": return(BuildPrimitiveMethod(rootType));

                case "concat": return(BuildPrimitiveMethod(rootType, rootType));

                case "contains": return(BuildPrimitiveMethod(tc.BOOLEAN, itemType));

                case "insert": return(BuildPrimitiveMethod(tc.VOID, tc.INTEGER, itemType));

                case "join": return(BuildPrimitiveMethodWithOptionalArgs(tc.STRING, 1, tc.STRING));

                case "pop": return(BuildPrimitiveMethod(itemType));

                case "remove": return(BuildPrimitiveMethod(tc.VOID, tc.INTEGER));

                case "reverse": return(BuildPrimitiveMethod(tc.VOID));

                case "shuffle": return(BuildPrimitiveMethod(tc.VOID));

                case "sort": return(BuildPrimitiveMethod(tc.VOID));

                // common mistakes
                case "count":
                case "size":
                    throw new ParserException(this.DotToken, "Lists do not have a ." + this.FieldToken.Value + "() method. Did you mean to use .length?");

                default:
                    throw new ParserException(this.DotToken, "Lists do not have that method.");
                }

            case ResolvedTypeCategory.DICTIONARY:
                ResolvedType keyType   = rootType.DictionaryKeyType;
                ResolvedType valueType = rootType.DictionaryValueType;
                switch (field)
                {
                case "length":
                    this.ResolvedType = tc.INTEGER;
                    return(this);

                case "clear": return(BuildPrimitiveMethod(tc.VOID));

                case "clone": return(BuildPrimitiveMethod(rootType));

                case "contains": return(BuildPrimitiveMethod(tc.BOOLEAN, keyType));

                case "get": return(BuildPrimitiveMethodWithOptionalArgs(valueType, 1, keyType, valueType));

                case "keys": return(BuildPrimitiveMethod(ResolvedType.ListOrArrayOf(keyType)));

                case "merge": return(BuildPrimitiveMethod(tc.VOID, rootType));

                case "remove": return(BuildPrimitiveMethod(tc.VOID, keyType));

                case "values": return(BuildPrimitiveMethod(ResolvedType.ListOrArrayOf(valueType)));

                default:
                    throw new ParserException(this.DotToken, "Dictionaries do not have that field.");
                }

            case ResolvedTypeCategory.CLASS_DEFINITION:
                throw new ParserException(this.DotToken, "Class definitions do not have that field.");

            case ResolvedTypeCategory.FUNCTION_POINTER:
                switch (field)
                {
                case "invoke":
                    return(BuildPrimitiveMethod(tc.ANY, ResolvedType.ListOrArrayOf(tc.ANY)));

                default:
                    throw new ParserException(this.DotToken, "Fucntions do not have that field.");
                }

            case ResolvedTypeCategory.INSTANCE:
                FieldDefinition fieldDef = rootType.ClassTypeOrReference.GetField(field, true);
                if (fieldDef != null)
                {
                    if (!Node.IsAccessAllowed(this, fieldDef))
                    {
                        ClassDefinition cd = fieldDef.ClassOwner;
                        throw new ParserException(FieldToken, "The field '" + cd.NameToken.Value + "." + this.FieldToken.Value + "' is not accessible from here due to its access scope.");
                    }

                    this.ResolvedType = fieldDef.ResolvedFieldType;
                    return(this);
                }
                FunctionDefinition funcDef = rootType.ClassTypeOrReference.GetMethod(field, true);
                if (funcDef != null)
                {
                    this.ResolvedType = ResolvedType.GetFunctionType(funcDef);
                    return(this);
                }
                throw new ParserException(this.DotToken, "The class '" + rootType.ClassTypeOrReference.NameToken.Value + "' does not have a field called '" + field + "'.");

            default:
                throw new ParserException(this.DotToken, "This field does not exist.");
            }
        }